Astral Realms Documentation Help

Overview

AstralRoomEditor is a development-only Paper/Purpur plugin for visually authoring "rooms" — the dungeon/lobby spaces used by other Astral Realms plugins (e.g. AstralTower, AstralDungeons). A builder walks through a data-driven flow: pick a gamemode and layout, place a fixed palette of components in-game with hotbar tools, and save the result as a reusable RoomBlueprint — a YAML file plus a saved WorldEdit schematic. The plugin disables itself on any server whose environment is not DEVELOPMENT (AstralRoomEditor.onEnable() checks AstralPaperAPI.serverInformation().environment()), so it never runs in production.

Data model

Everything a builder can place is declared in YAML, not code — adding a new gamemode, layout, or component type is a config change. Three definition types drive the editor:

Definition

File(s)

Declares

Component

components/*.yml (flat)

One placeable "tool": its placement shape (primitive), whether it needs a per-placement id, and how its hotbar item and in-world preview are built.

Gamemode

layouts/<gamemode>/gamemode.yml (optional)

A display name and default save settings inherited by every layout in that folder.

Layout

layouts/<gamemode>/<layout>.yml (one per file, gamemode.yml excluded)

The palette of component ids a room of this type may place (components), which of those are mandatory before saving (required), and its own save settings (override the gamemode defaults field-by-field).

Both registries (ComponentRegistry and GamemodeRegistry) are loaded fresh on onEnable and on /re reload, and are atomic: if any file in a folder fails to parse, the whole reload for that folder is aborted and the previously loaded definitions are kept, so a typo never leaves the editor with a half-loaded, broken palette. Individual component definitions that reference an unknown primitive, render strategy, or item strategy are skipped (logged, not fatal); layouts that reference a component id the component registry doesn't know are logged as a warning but still loaded.

Component placement primitives

Every component's primitive selects one of five RoomComponent implementations registered in RoomEditorRegistries, which controls how many clicks it takes to place and what geometry it stores:

Primitive

Class

Placement

Carries

position

PositionRoomComponent

One right-click

A single point + yaw/pitch.

typed-position

TypedPositionRoomComponent

One right-click

A point + yaw/pitch + a caller-supplied or auto-generated id (requires-id: true).

cuboid

CuboidRoomComponent

Two right-clicks (opposite corners)

A min/max corner pair.

oriented-cuboid

OrientedCuboidRoomComponent

Two right-clicks

A min/max corner pair + a Cardinal facing (from the player's yaw at the second click).

orientation

OrientationRoomComponent

One right-click

A point + yaw/pitch + a Cardinal facing (from the player's yaw at click time) — used for the room's overall entry orientation.

Components whose primitive is typed-position or a cuboid variant "require an id": the session auto-generates one (<componentId>_<n>, zero-based per component per player) unless the builder supplies one explicitly via /re set <component> <id>. Placing a component with a key that's already occupied in the session replaces the old one (and its render) rather than adding a second copy.

Session workflow

  1. /re create <gamemode> <layout> <name> starts a RoomEditSession for the player: validates the room name (^[a-zA-Z0-9_]{3,16}$), resolves the gamemode and layout, and gives the player one hotbar item per component id the layout allows (via ComponentItemService). A player can only have one active session at a time.

  2. Placement happens by right-clicking a block while holding a component's tool item (ComponentItemListener): the click is cancelled and routed to SessionService, which builds the right RoomComponent for the component's primitive and stores it in the session keyed by its id (or by the component id itself, for singletons). A live preview is drawn via render strategies for as long as the session is open. Left-clicking cancels a pending first click of a two-click (cuboid) placement. /re set <component> [id] places a component at the player's current location instead of by clicking.

  3. /re undo removes only the single most recently placed component (one level of undo, tracked per-player — placing a new component overwrites the "last placed" pointer).

  4. /re save validates that every component type listed in the layout's required list is present (matched by type, not by session key, so a typed/multi-instance component like door_0 satisfies a door requirement), then:

    • copies the region bounded by the layout's bounds-component (bounding_box by default) into a WorldEdit clipboard and writes it to the layout's schematic-dir in the configured schematic-format;

    • makes every placed component's position relative to that region's lower-north-east corner (RoomBlueprint#offsetBy), so the saved blueprint is location-independent;

    • serializes the result (via the layout's serializer, default by default) to <rooms-dir>/<filename>.yml, where filename supports the {name}, {layout}, {gamemode} placeholders (default "{name}").

    • Components listed in the layout's exclude-components are dropped from the saved blueprint (still used during editing, e.g. to compute the bounds, but not persisted).

  5. /re load <blueprint> offsets a saved blueprint's components to the player's current location, opens a new session pre-populated with them, gives back the hotbar items, and pastes the schematic at the player's location.

  6. /re cancel discards the session and its render without saving.

See Commands for the full subcommand list and Configuration for every YAML key.

Component rendering

While a session is open, RenderService runs an async repeating task (every 20 ticks) that calls onTick(...) on every placed component's render strategy, in addition to onAdd(...)/onRemove(...) fired immediately on placement/replacement and onSessionEnd(...) fired on cancel/save. Three render strategies ship built-in (registered by name in ComponentService's constructor):

Strategy

Applies to

Behavior

Options

block-display

position/orientation components

Spawns a hardened BlockDisplay entity showing a configured block at the point.

block (Material name, default BEACON).

entity

position/typed-position components

Spawns a hardened entity of a configured type, with an optional custom name and attribute modifiers (e.g. SCALE).

entity (EntityType, default ARMOR_STAND), name (optional custom name), attributes (map of Attribute name → double modifier value, added as an ADD_NUMBER modifier).

cuboid-outline

cuboid/oriented-cuboid components

Every tick, spawns a configured particle along the cuboid's block outline (no add/remove visuals).

particle (Particle name, default CRIT).

Hardened displays/entities are per-player and tracked so they can be individually removed when a component is replaced, or bulk-removed on session end. A component's hotbar item is built the same way — via a named item strategy (only simple ships, producing an unbreakable, lore-annotated tool item) resolved from the component definition's item.type.

Modules

Module

Contains

common

Platform-agnostic model/persistence: RoomComponent primitives, ComponentDefinition/LayoutDefinition/GamemodeDefinition, the registries, RoomBlueprint, and the Configurate type serializers. No Bukkit imports.

paper

The Bukkit plugin: services, listeners, the /roomeditor command, in-game rendering, and WorldEdit schematic I/O. Depends on common.

plugin.yml declares dependencies on AstralCore, FastAsyncWorldEdit, and packetevents.

Last modified: 25 July 2026