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 |
| One placeable "tool": its placement shape ( |
Gamemode |
| A display name and default save settings inherited by every layout in that folder. |
Layout |
| The palette of component ids a room of this type may place ( |
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 |
|---|---|---|---|
|
| One right-click | A single point + yaw/pitch. |
|
| One right-click | A point + yaw/pitch + a caller-supplied or auto-generated id ( |
|
| Two right-clicks (opposite corners) | A min/max corner pair. |
|
| Two right-clicks | A min/max corner pair + a |
|
| One right-click | A point + yaw/pitch + a |
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
/re create <gamemode> <layout> <name>starts aRoomEditSessionfor 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 (viaComponentItemService). A player can only have one active session at a time.Placement happens by right-clicking a block while holding a component's tool item (
ComponentItemListener): the click is cancelled and routed toSessionService, which builds the rightRoomComponentfor 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./re undoremoves only the single most recently placed component (one level of undo, tracked per-player — placing a new component overwrites the "last placed" pointer)./re savevalidates that every component type listed in the layout'srequiredlist is present (matched by type, not by session key, so a typed/multi-instance component likedoor_0satisfies adoorrequirement), then:copies the region bounded by the layout's
bounds-component(bounding_boxby default) into a WorldEdit clipboard and writes it to the layout'sschematic-dirin the configuredschematic-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,defaultby default) to<rooms-dir>/<filename>.yml, wherefilenamesupports the{name},{layout},{gamemode}placeholders (default"{name}").Components listed in the layout's
exclude-componentsare dropped from the saved blueprint (still used during editing, e.g. to compute the bounds, but not persisted).
/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./re canceldiscards 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 |
|---|---|---|---|
|
| Spawns a hardened |
|
|
| Spawns a hardened entity of a configured type, with an optional custom name and attribute modifiers (e.g. |
|
|
| Every tick, spawns a configured particle along the cuboid's block outline (no add/remove visuals). |
|
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 |
|---|---|
| Platform-agnostic model/persistence: |
| The Bukkit plugin: services, listeners, the |
plugin.yml declares dependencies on AstralCore, FastAsyncWorldEdit, and packetevents.