Astral Realms Documentation Help

Developer API

AstralHomes exposes its state through two services hung off the main plugin instance (HomeService, WarpService), per-player data replicated via AstralSync, two custom Bukkit events, and two menu actions registered against AstralCore's action registry. There is no static *API façade — external plugins go through the plugin instance directly.

Maven coordinates

<dependency> <groupId>com.astralrealms</groupId> <artifactId>homes</artifactId> <version>1.1-SNAPSHOT</version> <scope>provided</scope> </dependency>

Repository: https://maven.astralrealms.fr/repository/maven-public/.

Accessing the plugin

import com.astralrealms.homes.AstralHomes; AstralHomes plugin = AstralHomes.getPlugin(AstralHomes.class);

AstralHomes is annotated @Getter at the class level, so every field gets a fluent accessor:

Getter

Type

Use

plugin.homes()

HomeService

Teleport to / create / delete / look up personal homes.

plugin.warps()

WarpService

Teleport to / create / delete / look up global warps.

plugin.requests()

TeleportationRequestService

Send/accept/decline/cancel /tpa-style requests, including the cross-server pre-flight check.

plugin.warmup()

WarmupService

The shared action-bar warmup used before every non-instant teleport.

plugin.locations()

LocationService

The /back location stack (add/peek/pop, gated by homes.back).

plugin.configuration()

MainConfiguration

Parsed config.yml — see Configuration.

plugin.database()

DatabaseService

The MariaDB connection backing the warps table.

plugin.messaging()

MessagingService

The cross-server packet bus (see Cross-server messaging).

plugin.cache()

CacheService

Redis, backing RequestCacheRepository.

plugin.menus()

MenuContainer

AstralCore menu container — opens the plugin's menus/*.yml.

HomeService

Method

Returns

Use

teleport(Player player, Home home)

void

Warms up the player (warmup().warmup(...)), then teleports via the core TeleportationService to home.location() (a NetworkLocation, so this works across servers). Sends HOME_TELEPORT_SUCCESS/HOME_TELEPORT_FAILURE, or UNEXPECTED_ERROR if the service is missing or the future completes exceptionally.

set(Player player, String name)

void

Creates or overwrites a home named name at the player's current location. See SetHomeEvent for the exact ordering against validation. Throws IllegalStateException if the player has no HomePlayerData loaded — callers should only invoke this for an online, fully-joined player.

delete(Player player, Home home)

void

Removes home from the player's HomePlayerData, sends HOME_DELETED, then fires DeleteHomeEvent. Logs an error and sends UNEXPECTED_ERROR if the player has no data loaded.

findByName(UUID playerId, String name)

Optional<Home>

Case-insensitive lookup via findByPlayer.

findByPlayer(UUID playerId)

@Unmodifiable Set<Home>

All of a player's homes, or Set.of() if the player has no HomePlayerData loaded (never throws).

plugin.homes().findByName(player.getUniqueId(), "base") .ifPresent(home -> plugin.homes().teleport(player, home));

WarpService

Warps are global (not per-player) and backed by MariaDB; WarpService loads every row into an in-memory ConcurrentHashMap<String, Warp> (keyed by lowercase name) at construction and keeps it in sync with the database on every write.

Method

Returns

Use

findByName(String name)

Optional<Warp>

Case-insensitive in-memory lookup.

warps()

@Unmodifiable Map<String, Warp>

Every loaded warp, keyed by lowercase name.

set(Player player, String name)

void

Creates (or updates) a warp at the player's location, tagged with the server's AstralPaperAPI.serverInformation().group(). Persists via CrudRepository<Warp> (insert if new, else update), then updates the in-memory map and sends WARP_CREATED.

delete(Player player, Warp warp)

void

Deletes the row by uniqueId(), removes it from memory, sends WARP_REMOVED.

plugin.warps().findByName("spawn") .ifPresentOrElse( warp -> player.teleport(PaperAdapter.adapt(warp.location())), () -> HomesMessages.NO_SPAWN_SET.message(player));

Events

Both events live in com.astralrealms.homes.event and use the same fluent Lombok accessors as everything else in the package — player(), name(), location(), not getPlayer()/getName()/getLocation().

SetHomeEvent

Extends AbstractCancellableEvent (a Bukkit Cancellable with the usual isCancelled()/setCancelled(boolean)). Fired from HomeService.set after the existing-home lookup but before the per-player home limit check and before the home is actually created or persisted:

boolean existing = data.findHomeByName(name).isPresent(); SetHomeEvent event = new SetHomeEvent(player, name, location); if (!event.callEvent()) { HomesMessages.HOME_CREATION_CANCELLED.message(player); return; // limit check and creation never run }

Accessor

Type

Value

player()

Player

The player setting the home.

name()

String

The home name being set (already passed /sethome's length/character validation).

location()

Location

The player's current Bukkit Location at the moment of the call.

Cancelling (event.setCancelled(true)) skips the home limit check entirely and stops the home from being created or updated — the player receives HOME_CREATION_CANCELLED instead. This is the hook AstralTown uses to block /sethome inside claims the player doesn't have SET_HOME permission for.

DeleteHomeEvent

Extends AbstractEvent (not cancellable — informational only). Fired from HomeService.delete after the home has already been removed from HomePlayerData and the player has been sent HOME_DELETED:

Accessor

Type

Value

player()

Player

The player who deleted the home.

name()

String

The deleted home's name.

location()

Location

The home's last location, adapted back from its NetworkLocation via PaperAdapter.adapt(...).

@EventHandler public void onHomeDeleted(DeleteHomeEvent event) { // e.g. clean up per-plugin state keyed by home name }

Per-player data — HomePlayerData

Homes, /back history, and the TPA-toggle are not stored in MariaDB — they live in HomePlayerData, replicated across the network by AstralSync and looked up per-player:

import com.astralrealms.homes.storage.HomePlayerData; import com.astralrealms.sync.SyncAPI; SyncAPI.findData(player.getUniqueId(), HomePlayerData.class) .ifPresentOrElse(data -> { Set<Home> homes = data.homes(); // unmodifiable copy boolean acceptsRequests = data.teleportationRequestEnabled(); }, () -> { // Not loaded yet — the player's AstralSync snapshot hasn't been applied });

findData returns Optional.empty() whenever the snapshot isn't loaded (player hasn't finished joining, or no adapter data exists yet) — every read site in AstralHomes treats that as "act as if the player has no homes" rather than throwing, and callers should do the same.

Field

Type

Notes

homes

Set<Home>

Mutated via addHome(Home) (replaces any existing home with the same name, case-insensitive), removeHome(Home)/removeHome(String name), clearHomes(), findHomeByName(String). Read via homes() — returns an unmodifiable copy.

lastLocations

Map<NetworkLocation, Long>

The /back stack, keyed by location, valued by timestamp (ms). addLastLocation, popLastLocation()/peekLastLocation() (most recent by timestamp), pruneLastLocations(maxSize, minTimestamp). Read via lastLocations() — unmodifiable copy.

lastServersLocations

Map<String, MinecraftLocation>

Per-server-group "where was this player last seen" map, keyed "<serverId>:<world>". setLastServerLocation(server, location)/getLastServerLocation(server, world). Read via lastServersLocations() — unmodifiable copy. Drives the join-location restore behavior configured under restore-location-on-join-groups.

teleportationRequestEnabled

boolean

/tpatoggle state. Fluent getter/setter (teleportationRequestEnabled()/teleportationRequestEnabled(boolean)@Setter on the field). Defaults to true for a freshly-created player (see HomeSnapshotAdapter.create).

HomePlayerData also implements ComplexPlaceholder (namespace homes) exposing homes, lastLocation (→ peekLastLocation()), and teleportationRequestEnabled as placeholder sub-keys — see Placeholders.

Mutating these fields directly (addHome, removeHome, etc.) bypasses the confirmation messages, the SetHomeEvent/DeleteHomeEvent hooks, and the home limit check — prefer HomeService.set/delete for anything player-facing, and only touch HomePlayerData directly for admin tooling or migrations.

Wire format — HomeSnapshotAdapter

HomeSnapshotAdapter (registered via SyncAPI.registerAdapter(new HomeSnapshotAdapter(this)), key astralhomes:homes) is an UnloadableSnapshotAdapter<HomePlayerData> and defines the binary wire format other servers deserialize. serialize/deserialize must stay in this exact field order — this is a positional binary format, not a keyed one, so any AstralHomes version reading a snapshot written by another version must agree on the order:

// serialize(dataHolder, homePlayerData, binaryMessage) — in order: binaryMessage.writeSet(homePlayerData.homes(), (bm, home) -> { bm.writeString(home.name()); bm.writeNetworkLocation(home.location()); }); binaryMessage.writeMap(homePlayerData.lastLocations(), BinaryMessage::writeNetworkLocation, BinaryMessage::writeLong); binaryMessage.writeMap(homePlayerData.lastServersLocations(), BinaryMessage::writeString, BinaryMessage::writeMinecraftLocation); binaryMessage.writeBoolean(homePlayerData.teleportationRequestEnabled());
  1. homes — a set of (name: String, location: NetworkLocation) pairs.

  2. lastLocations — a map of NetworkLocation → long (timestamp).

  3. lastServersLocations — a map of String → MinecraftLocation ("server:world" key).

  4. teleportationRequestEnabled — a trailing boolean.

Two other adapter hooks worth knowing about:

  • create(Player player) — the default snapshot for a brand-new player: empty homes, empty location maps, teleportationRequestEnabled = true.

  • apply(Player player, HomePlayerData data) — runs on join. If the player wasn't already teleported on join by another system, and the current server's group is listed in restore-location-on-join-groups, it looks up data.getLastServerLocation(serverId, world) and teleports the player there if LocationUtils.isSafeLocation passes.

  • unload(Player player, HomePlayerData data) — runs on quit/unload: records the player's current location as their last-server-location, and (only if the player has homes.back) pushes it onto the /back stack.

AstralHomes registers two PaperActions against AstralCore's action registry in onEnable, for use from any menus/*.yml:

Registered as

Class

Scope

[connect-world]

ConnectWorldAction

registerActionGlobally — usable from any plugin's menu, not just AstralHomes'.

[delete-home]

DeleteHomeAction

registerAction — scoped to AstralHomes' own action registry.

connect-world

public record ConnectWorldAction(@Single PlaceholderWrapper<String> group, PlaceholderWrapper<String> world) implements PaperAction { ... }

Two placeholder-resolvable arguments — a server group and a world name within it. run resolves the TeleportationService, then warms the player up (plugin.warmup().warmup(...)) before calling service.teleport(player.getUniqueId(), group, world) — a cross-server teleport into a specific world on a specific group, independent of homes/warps.

actions: - "[connect-world] survival %player_last_world%"

delete-home

public record DeleteHomeAction(PlaceholderWrapper<Home> wrapper) implements PaperAction { ... }

Takes a single PlaceholderWrapper<Home> — resolved through the Home AstralAdapter /context machinery, typically bound to the home the menu slot represents. If the wrapper fails to resolve to a Home, the player is sent UNEXPECTED_ERROR and nothing happens; otherwise it calls plugin.homes().delete(player, home) (see HomeService — this fires DeleteHomeEvent).

Cross-server messaging

AstralHomes registers three packets on its own PacketRegistry in onEnable, exchanged over homes.teleportation.requests:

ID

Class

Direction / role

0x01

TeleportationRequestPacket

Sender's server → recipient's server. Wraps a TeleportationRequest(senderId, recipientId, here, timestamp). Sent via messaging().sendWithReply(EXCHANGE, ...) when the recipient isn't on the local server, as the pre-flight check for /tpa//tpahere.

0x02

TeleportationRequestStatusPacket

Reply to 0x01. Carries a Response enum (SUCCESS, REQUESTS_DISABLED, PROTECTED_WORLD, ALREADY_PENDING, FAILED) plus an optional worldPermission string (the permission node the sender needs, when the recipient is in a protected world).

0x03

TeleportationResponsePacket

Registered in the PacketRegistry (wraps a TeleportationRequest + accepted boolean) but not constructed anywhere in current plugin logic — the actual accept/decline teleport goes through the core TeleportationService.teleport(from, to) (and a separate com.astralrealms.core.packet.impl.teleportation.TeleportationResponsePacket type from AstralCore), not this class.

TeleportationRequestService's constructor registers an RpcPacketListener on the exchange: when it receives a TeleportationRequestPacket for a player who is online on the local server, it evaluates doesAcceptRequests and the destination world's protection permission, and replies with a TeleportationRequestStatusPacket. If the recipient isn't local, the listener returns null (no reply from that server).

Registering an additional packet follows the same pattern — call packetRegistry.registerPacket(id, YourPacket::new) in onEnable with a numeric ID that isn't already taken, and implement Packet#write/read against a BinaryMessage. IDs are not negotiated between servers, so a new ID must be assigned consistently across every server running the plugin.

ACF extension points

AstralHomes registers argument resolution for its own commands only (on its own PaperCommandManager instance) — these aren't shared infrastructure other plugins hook into directly, but the lookup pattern they use (homes()/warps()) is safe for any plugin with a runtime dependency on AstralHomes to copy into its own commands:

Registration

Class

Behavior

registerContext(Home.class, ...)

HomeContextResolver

Pops the next raw arg, resolves it via plugin.homes().findByName(player.getUniqueId(), name), throws InvalidCommandArgument if not found.

registerContext(Warp.class, ...)

WarpContextResolver

Pops the next raw arg, resolves it via plugin.warps().findByName(name), throws InvalidCommandArgument if not found.

registerAsyncCompletion("homes", ...)

HomeCompletionHandler

@homes tab-completion — the invoking player's own home names.

registerAsyncCompletion("warps", ...)

WarpCompletionHandler

@warps tab-completion — every warp name, network-wide.

Messaging convention

Never hardcode a user-facing string in code that touches AstralHomes. Add a new constant to the HomesMessages enum, add the matching key to messages.yml, and send it with HomesMessages.YOUR_KEY.message(player, placeholders) (or .component(placeholders) if you need the Component without sending it, e.g. to relay through ChatService to a player on another server). This keeps every string translatable and editable from messages.yml without a code change, and keeps placeholder substitution (%player_name%, %home_name%, …) consistent across every message the plugin sends.

Last modified: 25 July 2026