Astral Realms Documentation Help

Tower Instances

Once the bridge hands a party off to a tower server (see Overview: creating a tower instance), the paper module's InstanceService generates a LocalTowerInstance — a sequence of RoomInstances pasted into a fresh world — and drives the party through it room by room. This page covers room generation, mob scaling, phases, doors, and the instance lifecycle, plus the custom Bukkit events the paper module fires.

Room generation (rooms.yml)

rooms: lobby: id: "lobby" generation: min: 0 max: 0 chance: 100 monsters: id: "monsters" generation: min: 1 max: 3 chance: 80 monsters-1: id: "monsters-1" generation: min: 1 max: 2 chance: 60

Key

Type

Description

rooms.<key>.id

string

The room's blueprint id — must match a room blueprint that actually exists (validated at load: loadConfiguration throws IllegalStateException for any id RoomService#findByName can't resolve).

rooms.<key>.generation.min/.max

int

The inclusive range of sequence positions (0-based) this room is eligible to appear at.

rooms.<key>.generation.chance

double

Relative weight used to pick among the rooms eligible for a given position — not a percentage; it's only compared against the weights of other currently-eligible, not-yet-used rooms.

InstanceService#generateRoomSequence builds the sequence position by position, from 0 up to the highest generation.max across all configured rooms:

  1. At each position, filter to rooms that (a) haven't been used yet in this sequence and (b) have that position within [min, max].

  2. Pick one at random, weighted by chance, via a RandomCollection.

  3. If no room is eligible for a position, instance creation fails with an IllegalStateException.

Each room is used at most once per instance — a room's blueprint never repeats within the same run. Position 0 is always the lobby room in the shipped config (min: 0, max: 0, chance: 100), so it's the only room eligible there.

Mob scaling (mobs.yml)

room-health-multiplier: 5 # 5% health-multiplier-per-player: 50 # 50% more health per player in the room mobs: 1: count: 5 mobs: - "zombie" - "skeleton" 2: count: 7 mobs: - "spider" - "creeper"

Key

Type

Description

room-health-multiplier

int (percent)

Extra max-health percentage applied per room index — a room at index 3 gets room-health-multiplier * 3% bonus max health on its mobs (a multiplicative AttributeModifier, MULTIPLY_SCALAR_1, on Attribute.MAX_HEALTH).

health-multiplier-per-player

int (percent)

Extra max-health percentage per participant beyond the first — health-multiplier-per-player * (participants - 1)%.

mobs.<roomIndex>.count

int

Total number of mobs spawned for the room at that sequence index. Rooms with no matching index (e.g. the lobby, index 0) skip mob spawning entirely.

mobs.<roomIndex>.mobs

list of AstralMobs IDs

Mob pool; each spawn picks one ID at random from this list.

Both modifiers stack multiplicatively (each is its own AttributeModifier, both MULTIPLY_SCALAR_1) and are applied on EntitySpawnEvent for every living entity spawned in a tower world, then the entity's health is set back to its (now higher) max health. Mobs spawn a few at a time on a repeating scheduler (starting at 1, incrementing every other tick) at the room's MOB position components, cycling through the available spawn points; the boss room additionally spawns at its dedicated BOSS position component. Once every mob for the room has spawned, the room moves to RoomPhase.COMBAT.

Room phases

Each RoomInstance tracks a RoomPhase:

Phase

Meaning

SPAWNING

Mobs are still being spawned in over time.

COMBAT

All configured mobs have spawned; the room is cleared once none remain alive.

REWARDS

The room's reward is being set up (spawning artifacts, coins, or shop offers) based on the room's rewardType.

END_REWARDS

A reward chest/entity is available to interact with.

COMPLETE

The room's doors are open and the party can advance.

InstanceService#changePhase fires RoomStateChangeEvent before applying each transition, then branches on the new phase:

  • On REWARDS: the lobby room skips straight to COMPLETE; a shop room spawns shop offers and also skips to COMPLETE; otherwise the room's rewardType decides whether artifacts spawn (staying in REWARDS until every participant claims one, then ArtifactService#claimArtifact advances the room straight to COMPLETE — not END_REWARDS, which is only used for the death/end-of-run reward chest, see Instance lifecycle) or coins spawn (advancing straight to COMPLETE). A room with no reward and no next room ends the instance (EndCause.COMPLETED); a room with no reward but more rooms ahead just advances to COMPLETE.

  • On COMPLETE: RoomService#openDoors opens the room's doors. See Doors below.

Rooms with a mob set that spawns zero mobs (or a shop room) skip straight from SPAWNING to REWARDS.

Doors

A cleared room's COMPLETE phase opens every DOOR component in its blueprint, each tagged with a RoomRewardType describing what the next room offers if the party walks through that door:

  • If the next room's blueprint type is LOBBY, its door's reward type is forced to NONE.

  • If it's SHOP, forced to SHOP; if BOSS, forced to BOSS.

  • Otherwise, doors are assigned ARTIFACTS or COINS, trying to give each door a different type first before falling back to a fully random pick once both have been used once.

Walking into a door's area (PlayerMoveEvent, block-change granularity) fires RoomDoorEnterEvent; if not cancelled, the next room's rewardType is set from the door's type and the instance advances to it (InstanceService#nextRoom), which re-runs room setup for the new room.

Instance lifecycle

State (TowerState)

Meaning

CREATING

The LocalTowerInstance's initial state (field default), before world/room setup finishes.

CREATED

World pasted, rooms built, first room set up. Set by InstanceService#create.

STARTING

Defined on the enum but not currently set anywhere in InstanceService.

PLAYING

Set once the party leaves the lobby room (RoomType.LOBBY) via nextRoom.

ENDED

Defined on the enum but not currently set anywhere in InstanceService — instances go straight from PLAYING to being destroyed.

DELETING

Defined on the enum but not currently set anywhere in InstanceService.

An instance ends (InstanceService#endInstance, EndCause) when:

Cause

Trigger

COMPLETED

The party finishes the last room's rewards, or claims every reward chest in it.

PARTY_DISBANDED

Defined on the enum; not currently fired from within this plugin's own code.

TIMEOUT

Defined on the enum; not currently fired from within this plugin's own code.

PLAYER_QUIT

Fired when a participant quits and no participants remain in the instance afterward.

DEATH

Fired on PlayerDeathEvent for a participant, one tick after they're force-respawned.

If the ending room is the instance's last room and the cause is COMPLETED, or the cause is DEATH, the instance transitions to RoomPhase.END_REWARDS and spawns reward chests instead of destroying immediately — see Loot, Shop & Rewards. Otherwise (any other COMPLETED, or PLAYER_QUIT) the instance is destroyed after a 300-tick delay: participants are teleported to fallback-group, the instance is removed from the repository, and its world is unloaded and deleted.

On disable, AstralTower#onDisable calls InstanceService#deleteAllInstances() to tear down every active instance before the plugin shuts down.

Custom events

The paper module fires the following custom Bukkit events. All extend AbstractEvent (com.astralrealms.core.paper.model.event) and can be listened to with @EventHandler as usual.

RoomStateChangeEvent

Fully qualified name: com.astralrealms.tower.event.room.RoomStateChangeEvent (extends RoomEvent).

Fired by InstanceService#changePhase, before the room's phase is actually updated. Not cancellable — notification only.

Field

Type

Description

instance

LocalTowerInstance

Inherited from RoomEvent.

room

RoomInstance

Inherited from RoomEvent.

oldPhase

RoomPhase

The phase being transitioned from.

newPhase

RoomPhase

The phase being transitioned to.

@EventHandler public void onRoomStateChange(RoomStateChangeEvent event) { // event.oldPhase() -> event.newPhase() }

RoomClearedEvent

Fully qualified name: com.astralrealms.tower.event.room.RoomClearedEvent (extends RoomEvent).

Fired by MobListener#onEntityDeath, 10 ticks after the last mob in a room dies, if the room is still cleared() and still in RoomPhase.COMBAT at that point (a delayed re-check to avoid firing mid-spawn-wave). Not cancellable. RoomListener listens for this to run config.yml's room-complete-actions against every participant, then transitions the room to RoomPhase.REWARDS.

Field

Type

Description

instance

LocalTowerInstance

Inherited from RoomEvent.

room

RoomInstance

Inherited from RoomEvent.

RoomDoorEnterEvent

Fully qualified name: com.astralrealms.tower.event.room.player.RoomDoorEnterEvent (extends RoomEvent, implements Cancellable).

Fired by DoorListener#onPlayerMove when a player steps into an open door's area cuboid in a COMPLETE room. Cancellable — cancelling prevents the door's reward-type assignment and the instance from advancing to the next room.

Field

Type

Description

instance

LocalTowerInstance

Inherited from RoomEvent.

room

RoomInstance

Inherited from RoomEvent.

player

Player

The player who entered the door.

door

DoorInstance

The door entered (type = the RoomRewardType it grants the next room, area = its cuboid).

@EventHandler public void onDoorEnter(RoomDoorEnterEvent event) { if (event.door().type() == RoomRewardType.BOSS) { // ... } }

PlayerJoinTowerInstanceEvent/PlayerQuitTowerInstanceEvent

Fully qualified names: com.astralrealms.tower.event.instance.PlayerJoinTowerInstanceEvent/...PlayerQuitTowerInstanceEvent (extend AbstractEvent).

Fired by PlayerListener on PlayerJoinEvent (after adding the player to instance.participants()) and PlayerQuitEvent (before removal) respectively, only when the joining/quitting player's world resolves to an active tower instance. Not cancellable. PlayerDataListener uses these to load/unload the player's in-session PlayerData (coins, mobs killed) — see Placeholders.

Field

Type

Description

player

Player

The joining/quitting player.

instance

LocalTowerInstance

The tower instance their world belongs to.

Last modified: 25 July 2026