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)
Key | Type | Description |
|---|---|---|
| string | The room's blueprint id — must match a room blueprint that actually exists (validated at load: |
| int | The inclusive range of sequence positions (0-based) this room is eligible to appear at. |
| 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:
At each position, filter to rooms that (a) haven't been used yet in this sequence and (b) have that position within
[min, max].Pick one at random, weighted by
chance, via aRandomCollection.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)
Key | Type | Description |
|---|---|---|
| int (percent) | Extra max-health percentage applied per room index — a room at index |
| int (percent) | Extra max-health percentage per participant beyond the first — |
| int | Total number of mobs spawned for the room at that sequence index. Rooms with no matching index (e.g. the lobby, index |
| 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 |
|---|---|
| Mobs are still being spawned in over time. |
| All configured mobs have spawned; the room is cleared once none remain alive. |
| The room's reward is being set up (spawning artifacts, coins, or shop offers) based on the room's |
| A reward chest/entity is available to interact with. |
| 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 toCOMPLETE; a shop room spawns shop offers and also skips toCOMPLETE; otherwise the room'srewardTypedecides whether artifacts spawn (staying inREWARDSuntil every participant claims one, thenArtifactService#claimArtifactadvances the room straight toCOMPLETE— notEND_REWARDS, which is only used for the death/end-of-run reward chest, see Instance lifecycle) or coins spawn (advancing straight toCOMPLETE). 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 toCOMPLETE.On
COMPLETE:RoomService#openDoorsopens 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 toNONE.If it's
SHOP, forced toSHOP; ifBOSS, forced toBOSS.Otherwise, doors are assigned
ARTIFACTSorCOINS, 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 ( | Meaning |
|---|---|
| The |
| World pasted, rooms built, first room set up. Set by |
| Defined on the enum but not currently set anywhere in |
| Set once the party leaves the lobby room ( |
| Defined on the enum but not currently set anywhere in |
| Defined on the enum but not currently set anywhere in |
An instance ends (InstanceService#endInstance, EndCause) when:
Cause | Trigger |
|---|---|
| The party finishes the last room's rewards, or claims every reward chest in it. |
| Defined on the enum; not currently fired from within this plugin's own code. |
| Defined on the enum; not currently fired from within this plugin's own code. |
| Fired when a participant quits and no participants remain in the instance afterward. |
| Fired on |
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 |
|---|---|---|
|
| Inherited from |
|
| Inherited from |
|
| The phase being transitioned from. |
|
| The phase being transitioned to. |
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 |
|---|---|---|
|
| Inherited from |
|
| Inherited from |
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 |
|---|---|---|
|
| Inherited from |
|
| Inherited from |
|
| The player who entered the door. |
|
| The door entered ( |
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 |
|---|---|---|
|
| The joining/quitting player. |
|
| The tower instance their world belongs to. |