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
Repository: https://maven.astralrealms.fr/repository/maven-public/.
Accessing the plugin
AstralHomes is annotated @Getter at the class level, so every field gets a fluent accessor:
Getter | Type | Use |
|---|---|---|
|
| Teleport to / create / delete / look up personal homes. |
|
| Teleport to / create / delete / look up global warps. |
|
| Send/accept/decline/cancel |
|
| The shared action-bar warmup used before every non-instant teleport. |
|
| The |
|
| Parsed |
|
| The MariaDB connection backing the |
|
| The cross-server packet bus (see Cross-server messaging). |
|
| Redis, backing |
|
| AstralCore menu container — opens the plugin's |
HomeService
Method | Returns | Use |
|---|---|---|
|
| Warms up the player ( |
|
| Creates or overwrites a home named |
|
| Removes |
|
| Case-insensitive lookup via |
|
| All of a player's homes, or |
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 |
|---|---|---|
|
| Case-insensitive in-memory lookup. |
|
| Every loaded warp, keyed by lowercase name. |
|
| Creates (or updates) a warp at the player's location, tagged with the server's |
|
| Deletes the row by |
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:
Accessor | Type | Value |
|---|---|---|
|
| The player setting the home. |
|
| The home name being set (already passed |
|
| The player's current Bukkit |
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 |
|---|---|---|
|
| The player who deleted the home. |
|
| The deleted home's name. |
|
| The home's last location, adapted back from its |
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:
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 |
|---|---|---|
|
| Mutated via |
|
| The |
|
| Per-server-group "where was this player last seen" map, keyed |
|
|
|
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:
homes— a set of(name: String, location: NetworkLocation)pairs.lastLocations— a map ofNetworkLocation → long(timestamp).lastServersLocations— a map ofString → MinecraftLocation("server:world"key).teleportationRequestEnabled— a trailingboolean.
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 inrestore-location-on-join-groups, it looks updata.getLastServerLocation(serverId, world)and teleports the player there ifLocationUtils.isSafeLocationpasses.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 hashomes.back) pushes it onto the/backstack.
Menu actions
AstralHomes registers two PaperActions against AstralCore's action registry in onEnable, for use from any menus/*.yml:
Registered as | Class | Scope |
|---|---|---|
|
|
|
|
|
|
connect-world
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.
delete-home
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 |
|---|---|---|
|
| Sender's server → recipient's server. Wraps a |
|
| Reply to |
|
| Registered in the |
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 |
|---|---|---|
|
| Pops the next raw arg, resolves it via |
|
| Pops the next raw arg, resolves it via |
|
|
|
|
|
|
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.