Astral Realms Documentation Help

Portals & Keys

Everything on this page lives on the bridge module (AstralDungeons-Bridge, data folder plugins/AstralDungeons-Bridge/), and only comes alive on a server whose group matches portal-group in its config.yml. It covers how a party gets into a dungeon; how the dungeon is generated once they are in is Dungeon & Room Blueprints, a different file format on a different server.

Bridge blueprints (blueprints/)

One YAML file per dungeon, loaded by the bridge's BlueprintService on enable and keyed by its own id field. A duplicate id is logged as a warning and the later file is skipped.

id: "crypt_of_ashes" name: "<dark_red>Crypt of Ashes" key: material: TRIPWIRE_HOOK name: "<gold>Crypt of Ashes Key" lore: - "<gray>Opens the Crypt of Ashes." portal: min: world: "world" x: 120 y: 64 z: -40 max: world: "world" x: 124 y: 68 z: -36 particle: SOUL_FIRE_FLAME minimum-players: 2 maximum-players: 4 requirements: - "[has-permission] dungeons.enter.crypt"

Field

Type

Description

id

String

The blueprint id. Must match the id of a dungeon blueprint on the dungeon servers — this is the string sent in CreateDungeonRequestPacket, and the string a dungeon server matches against its own blueprints/ folder. A mismatch means every server ignores the request and the party gets creation-failure.

name

ComponentWrapper (MiniMessage)

Display name, exposed as %blueprint_name% in the entry messages.

key

ItemStackWrapper

The item that acts as this dungeon's key. See Keys.

portal

Cuboid

The region that triggers entry. Two min/max location nodes, each world + x/y/z (yaw/pitch optional and unused here); the corners are normalised, so which is which does not matter. Deserialized by AstralCore's CuboidTypeSerializer.

requirements

PaperRequirementList

Extra AstralCore requirements every party member must satisfy, evaluated after the key check. May be omitted or empty.

minimum-players

int

Smallest party (leader included) allowed to enter.

maximum-players

int

Largest party (leader included) allowed to enter.

particle

Bukkit Particle

Ambient particle rendered inside the portal region. Omit to render nothing. See Portal particles.

There is no validation pass beyond the duplicate-id check: a blueprint whose id matches no dungeon server, or whose portal sits in a world that does not exist on this server, loads without complaint and simply never works.

Keys

A key is an ordinary item stack built from the blueprint's key block with one addition: BlueprintService stamps the persistent-data key dungeons:blueprint (a STRING) with the blueprint id. That stamp — not the material, name or lore — is what identifies a key, so two blueprints may share an appearance and remain distinguishable, and a hand-crafted look-alike is not a key.

Keys are required to enter and consumed on completion, but not at the portal: the key check at the portal only verifies possession. The stack is taken later, on the lobby, when the run's rewards are claimed — PlayerDataService#claimRewards scans the returning player's inventory for a key matching the blueprint they ran and removes exactly one (decrementing the stack if it holds more). A player who never reaches the reward claim keeps their key.

Obtaining keys — the dungeons.keys supplier

The bridge registers an AstralCore ItemStackSupplier under the namespace dungeons.keys, so a stamped key can be produced from any config that accepts an item:

item-stack: material: "dungeons.keys-crypt_of_ashes"
/give Steve dungeons.keys-crypt_of_ashes 3

The supplier completes to every loaded blueprint id, and implements the reverse lookup too — an existing stack resolves back to dungeons.keys:<blueprint id>, so crates, shops and quest rewards that ask "what item is this?" identify keys correctly. An unknown id yields no item rather than an error.

Entering a portal

PortalListener reacts to PlayerMoveEvent (explicit block changes only) and fires once per entry: the portal the player currently stands in is remembered, so walking around inside it does not retrigger, and leaving re-arms it. While a create request is in flight for a player, every further move is ignored.

The gates run in this order. Each of the first four sends its message and bumps the player back out — a velocity impulse opposite to the direction they walked in, plus an enderman-teleport sound:

  1. A server must be available. ServerService#findEmptiest(id) must find a dungeon server that advertises this blueprint and is below its max-instances. → no-available-server

  2. The player must be in a party. → no-party

  3. The player must be the party leader. A member who walks in gets bounced; only the leader can pull the party in. → not-party-leader

  4. Party size must fit. party.members().size() + 1 must be within [minimum-players, maximum-players]. → invalid-party-size (with %min%/%max%)

  5. Every member must pass the requirements. Each member — leader included — must hold a key for this blueprint and satisfy the blueprint's requirements list. → failed-requirements

Only step 5 is asynchronous, and it is the point where the player is marked "processing". Note that a failure at step 5 does not bump the player back out; they stay standing in the portal, and must step out and back in to retry.

Once every gate passes, the player gets creation-start and the create RPC goes out — see Overview for the rest of the handshake.

Checking requirements across servers

A party's members may be spread across the network, so the key/requirement check cannot assume they are local. RequirementsService#checkForRequirements resolves each member individually:

  • Player is online here → checked locally, synchronously.

  • Player is elsewhere → a CheckRequirementsRequestPacket{playerId, blueprintId} goes out on the dungeons.requirements RPC exchange; whichever server has that player replies with a CheckRequirementsResponsePacket carrying the same local check's result.

The party passes only if every member's future resolves true. A member nobody can resolve (offline, or on a server that does not reply) never completes a true, so the party does not get in.

The local check itself is deliberately strict: no key means an immediate false, and a requirement list that throws while evaluating counts as unmet (the player is shown unexpected-error and the failure is logged) rather than being waved through.

Portal particles

When the server's group matches portal-group, a task runs every 5 ticks (starting 5 seconds after enable) and, for each blueprint that declares a particle, spawns 5 particles at the portal cuboid's centre with the offset set to half the region's width/height/length — so the particles fill the region rather than sitting at a point. They are forced (rendered even with reduced particle settings) and sent only to players within 64 blocks.

A blueprint with no particle is skipped, so an invisible portal is a supported configuration.

Last modified: 25 September 2026