Astral Realms Documentation Help

Warps & Spawn

Warps are global, network-wide teleport destinations — unlike homes, they aren't tied to a player or a server group. They're persisted in MariaDB (warps table, via a CrudRepository<Warp>) and mirrored into an in-memory ConcurrentHashMap<String, Warp> keyed by lowercase name. The map, not the database, is what every command and listener actually reads from at runtime; the repository is only touched on create/update/delete. Spawn is not a separate concept — it's simply the warp literally named spawn.

Model

The Warp entity (@Entity("warps")) maps onto the warps table:

Java field

Column

Type

Notes

uniqueId

id (@Id)

CHAR(36)

Primary key. Always set explicitly with UUID.randomUUID() when a warp is first created — the column's DEFAULT (UUID()) is never relied on.

name

name

VARCHAR(16), UNIQUE

Warp name, exactly as typed to /warp set. Not length- or character-validated by the plugin before insert (contrast /sethome, which rejects names over 16 characters or containing invalid characters — see Homes).

group (@Column("server_group"))

server_group

TEXT

Server group the warp was created on, or last overwritten on (AstralPaperAPI.serverInformation().group()). Drives which server a teleport is routed to.

x, y, z

x, y, z

DOUBLE

Location coordinates.

yaw, pitch

yaw, pitch

FLOAT

Look direction.

world

world

TEXT

World name on the owning server.

(unmapped)

created_at, updated_at

TIMESTAMP

Managed by MariaDB (DEFAULT CURRENT_TIMESTAMP/ON UPDATE CURRENT_TIMESTAMP). Not read back onto the Warp entity — the plugin never looks at them.

Warp.location()/Warp.location(MinecraftLocation) bundle x/y/z/yaw/pitch/world into (and out of) a single MinecraftLocation, which is what gets handed to the teleportation service. See Configuration for the raw schema.sql table definition and how database.properties is provisioned.

Loading

WarpService's constructor — run once, during onEnable — calls repository.findAll() asynchronously, logging Loading warps... and then, on completion, the count and elapsed time. Every returned row is put into the in-memory map keyed by warp.name().toLowerCase(). Because the load is asynchronous, the map can briefly be empty immediately after onEnable returns; in practice it fills before players can reach /warp or /spawn. WarpService.warps() exposes an unmodifiable view of the same map (used by tab completion).

Creating and updating a warp

/warp set <name>

Permission: warps.set. Tab-completes existing names via @warps. WarpService.set captures the player's current location (PaperAdapter.toMinecraftLocation) and the current server's group, then branches on whether name.toLowerCase() is already a key in the map:

  • New name — builds a fresh Warp with a new random UUID and inserts it (repository.insert).

  • Existing name — looks the warp up case-insensitively and mutates its location and group in place, then persists with repository.update. The warp keeps its original uniqueId and its original name casing — retyping /warp set Spawn against an existing spawn warp moves it but does not rename it to Spawn. There is no separate rename command; the only way to change a warp's name is to delete it and create it again under the new name.

Either path sends the same message on success — WARP_CREATED, with placeholders %warp_name%, %warp_x%, %warp_y%, %warp_z% — there is no distinct "warp updated" message. A repository failure logs the error and sends UNEXPECTED_ERROR instead. Once the database write completes, the in-memory map is updated in the same callback, so /warp set never leaves the map and the database out of sync with each other.

Deleting a warp

/warp delete <name>

Aliases: del, remove, rm. Permission: warps.delete. The <name> argument is typed Warp, resolved by WarpContextResolver (see Tab completion and argument resolution) — an unknown name never reaches the command method at all. WarpService.delete removes the row via repository.delete(warp.uniqueId()), then removes the entry from the in-memory map, then sends WARP_REMOVED. A repository failure logs the error and sends UNEXPECTED_ERROR instead, leaving the warp in memory and in the database untouched.

Teleporting to a warp

/warp <name>

No permission node. <name> resolves to a Warp the same way /warp delete does. The command runs the warp through the shared warmup first (see Warmup — instant if the player's resolved duration is zero), then calls:

teleportationService.teleport(player.getUniqueId(), warp.group(), warp.location())

This is the TeleportationService overload keyed by server group plus a MinecraftLocation, so it's cross-server aware by construction — the plugin never checks whether the warp's group matches the server the player is currently on. On completion:

Outcome

Message

The future completes exceptionally

UNEXPECTED_ERROR (and the exception is logged)

response.success() is true

WARP_TELEPORT_SUCCESS

response.success() is false

WARP_TELEPORT_FAILURE

WARP_TELEPORT_FAILURE covers every non-success TeleportationResponse alike (TIMEOUT, SERVER_OFFLINE, PLAYER_NOT_FOUND, SERVER_NOT_FOUND, NO_AVAILABLE_SERVER, …) — the command does not distinguish between them.

Spawn

/spawn

No permission node, player-only. SpawnCommand calls WarpService.findByName("spawn") — the same case-insensitive lookup as everything else, so a warp created as /warp set Spawn still satisfies it. If it exists, the player goes through the identical warmup-then-teleport flow as /warp <name>, replying with SPAWN_TELEPORT_SUCCESS or SPAWN_TELEPORT_FAILURE. If no warp named spawn exists, the player receives NO_SPAWN_SET and nothing is attempted. There is no dedicated spawn-location config key — an admin configures spawn purely by standing where they want it and running /warp set spawn.

Vanilla respawn and the fall safety net

SpawnListener (registered alongside the other listeners in onEnable) hooks the spawn warp into two more places beyond the /spawn command:

  • AsyncPlayerSpawnLocationEvent — on every vanilla respawn calculation, the listener looks up the spawn warp. If it's missing, a warning is logged and vanilla respawn logic is left alone. If it exists and its stored group equals the current server's group, the event's spawn location is overridden to the warp's location — so beds/respawn anchors are ignored in favor of the group's configured spawn. On a server whose group differs from the one the spawn warp was set on, this override does not apply.

  • PlayerMoveEvent fall/void safety net — skipped entirely if the current server's environment is Environment.DEVELOPMENT, or if the current server's group is not literally "spawn" (a hardcoded literal, independent of whatever group the spawn warp itself was saved under). Otherwise, the first move event where a player's Y position is below -50 or their accumulated fall distance exceeds 70 blocks triggers an immediate plain player.teleport(...) to the spawn warp's location — provided that warp's stored group also matches the current server's group. This path is silent (no message), synchronous, and does not go through WarmupService or TeleportationService — it only ever moves the player within the same server.

Tab completion and argument resolution

Two ACF hooks back every warp-name argument:

  • @warps (WarpCompletionHandler, registered as registerAsyncCompletion("warps", ...)) returns plugin.warps().warps().keySet() — every currently loaded warp name, lowercase, unfiltered by permission. Used by /warp <name>, /warp set <name>, and /warp delete <name>.

  • WarpContextResolver (registerContext(Warp.class, ...)) backs any command parameter typed Warp/warp <name> and /warp delete <name>. It pops the first argument, resolves it with WarpService.findByName (case-insensitive), and throws InvalidCommandArgument("Warp not found: <name>") if nothing matches, which ACF surfaces to the player as a command error before the command method ever runs.

/warp set <name> takes name as a plain String instead, since it must accept names that don't exist yet.

Message placeholders

Each warp/spawn message is built with a fresh PlaceholderContainer seeded with the relevant Warp for that call — these tokens only resolve inside their own message template in messages.yml, unlike the globally-registered %homes_...% tokens on Placeholders:

Message

Placeholders

WARP_CREATED

%warp_name%, %warp_x%, %warp_y%, %warp_z%

WARP_REMOVED

%warp_name%

WARP_TELEPORT_SUCCESS

%warp_name%

WARP_TELEPORT_FAILURE

%warp_name%

NO_SPAWN_SET

SPAWN_TELEPORT_SUCCESS

SPAWN_TELEPORT_FAILURE

See Commands for the full /warp//spawn syntax and permission summary, and Overview for how warp storage compares to home/last-location storage.

Last modified: 25 July 2026