Islands
An island is the persisted unit of skyblock gameplay: a database row (name, lock state, level, spawn pose) plus, while active, a loaded SlimeWorld instance. This page covers the island lifecycle, blueprints, world management, multi-server hosting, cobblestone generators, block values, upgrades, the island-scoped GUI surface, and cross-server persistence. For the role/permission/ settings model see Roles, Permissions & Settings; for membership, co-ops and invitations see Members, Co-ops & Invitations.
IslandService (islands()) owns creation, deletion and spawn resolution; WorldService (worlds()) owns the underlying SlimeWorld. The three lifecycle entry points below are all reachable from /is create|delete|go.
Lifecycle
Create
SkyblockCommand#onCreate calls IslandService#create(Player, String name, IslandBlueprint blueprint):
Name resolution — a null/blank
namefalls back to the player's username; otherwise it's trimmed.Uniqueness check —
IslandRepository#existsByNamechecks the in-memory name index first, falling back toSELECT 1 FROM islands WHERE name = ?on a miss. If the name is taken,ASMessages.NAME_ALREADY_TAKENis sent and creation stops.Row build — a new
Islandis built in memory:UUID.randomUUID(), the resolved name,locked = true,level = 0, and the spawn pose copied from the blueprint'sspawn-location.Transactional persist —
IslandRepository#createinserts the island row, every seeded role fromroles.yml(RoleService#defaultRoleSeeds), and the owner member in one JDBC transaction. Either everything commits or nothing does.World creation — only once that transaction commits does
WorldService#create(islandId, blueprint)clone the blueprint's slime-world template into a brand-new world named after the island's UUID (see Blueprints and Island worlds).Rollback on failure — if world creation throws or returns
null, the just-inserted island row (and its roles/owner) is deleted so a failed creation never leaves an island with no world behind.On success — the player is teleported to the new world's spawn,
ASMessages.ISLAND_CREATEDis sent, andIslandCreateEventfires.
Go / spawn
SkyblockCommand#onGo calls IslandService#spawnIsland(Island), which resolves where the island's world is (or should be) hosted and returns a NetworkLocation; the command then hands that to AstralCore's TeleportationService for the actual — possibly cross-server — teleport. The whole resolution is bounded by a 15-second timeout. See Multi-server hosting for the exact server-selection order.
Delete
SkyblockCommand#onDelete calls IslandService#delete(Player, Island):
IslandRepository#deleteremoves the database row, invalidates the island from the local and shared cache, and broadcasts the deletion (see Persistence & cross-server sync).Only after that succeeds,
WorldService#delete(islandId)broadcasts anIslandDeletePacket(see Multi-server hosting) so whichever server currently hosts the world drops it without saving, then unloads/deletes it locally and clears the host-server mapping.On success:
ASMessages.ISLAND_DELETEDis sent andIslandDeletedEventfires.
Neither the command nor the service performs an ownership or permission check — see Commands.
Blueprints
A blueprint is a named template an island is created from: a slime-world source, a spawn pose, and a menu icon. Blueprints live under plugins/AstralSkyblock/blueprints/*.yml, loaded by BlueprintService#load() (ConfigurationManager#loadFolder) — called on plugin enable and on every /is reload.
Field (Java) | YAML key | Type | Notes |
|---|---|---|---|
|
| String | Blueprint identifier. Tab-completed on |
|
| boolean | Exactly one loaded blueprint must be flagged |
|
| String | Filename of the slime-world template under |
|
|
| Becomes the new island's stored spawn pose. Only |
|
|
| Menu icon, e.g. rendered by the |
BlueprintService methods:
Method | Returns | Behavior |
|---|---|---|
| — | Clears and reloads every |
|
| Lookup by id, e.g. used by |
|
| Every loaded blueprint. |
|
| Every loaded blueprint id — backs the |
|
| The blueprint flagged |
Island worlds
WorldService is the AdvancedSlimePaper (ASP) integration layer. It reads blueprint templates from a read-only FileLoader over sourceWorlds/, and reads/writes live island worlds through a MysqlLoader configured from loader.yml, keyed by the island's UUID string. WorldService#load() initializes that MySQL loader — idempotent, so a second call (e.g. from /is reload) is a no-op — and, only on servers where SkyblockConfiguration#isIslandServer() is true, starts a 30-second idle-unload sweep task.
Method | Behavior |
|---|---|
| Clones the blueprint's template world under the island's UUID on the MySQL loader, loads it into Bukkit, saves it once, and returns the |
| Returns the already-loaded instance if present. Otherwise coalesces concurrent callers (e.g. two visitors, or a visit racing a cross-server load request) onto one in-flight future so |
| Saves the loaded instance via ASP. Fails if the world isn't currently loaded on this server. |
| Unloads the Bukkit world, drops it from the loaded-world/name maps, and clears the host-server mapping. |
| Broadcasts an |
On every successful load, IslandSettingsListener.applyEnvironment re-applies the island's actual IslandSettings to the Bukkit world (overriding some of the fixed defaults below — e.g. PvP), the current server claims the host-server slot via ServerService#setHostServer, and IslandWorldLoadedEvent fires. On unload, IslandWorldUnloadedEvent fires — but only if the island is present in the local island cache; otherwise a warning is logged and the event is skipped.
Every cloned/loaded island world gets the same fixed ASP property map (not currently configurable per blueprint or per island): difficulty normal, monsters and animals allowed, no dragon battle, world-level PvP off (overridden per-island by settings), environment NORMAL, world type DEFAULT, default biome minecraft:plains, and block ticks/fluid ticks/POI saving all disabled.
Idle-unload sweep
sweepIdleWorlds() runs every 30 seconds on island servers. A loaded world with zero online players starts a timer; once it has been empty for world-idle-unload-seconds (config.yml; 0 /absent falls back to 300s, a negative value disables the sweep entirely) it is unloaded, freeing the server's island slot. A player rejoining before the grace period elapses resets the timer. On plugin shutdown, every currently-loaded world is saved and its host-server mapping cleared (best-effort, 5-second timeout) before the loader is closed, so a restart doesn't leave stale island→server routing pointing at a dead server.
Multi-server hosting
ServerService (servers()) tracks which island server currently hosts each island, and how full each island server is.
Method | Behavior |
|---|---|
| Fetches every published |
| Writes |
| Reads that hash; |
| Removes the hash entry. Called on unload, delete, and plugin shutdown. |
On island servers, ServerService also publishes a self-reported IslandServer(uniqueId, loadedIslands, maximumIslands) heartbeat every 300 ticks (15s) into a 1-minute-TTL cache (SERVER_CACHE_KEY, skyblock:servers); maximumIslands comes from config.yml's maximum-islands.
IslandService#spawnIsland(Island) resolves a NetworkLocation in this order:
findHostServer— if the island is already hosted somewhere, resolve the location against that server.findEmptiestServer— otherwise pick the least-loaded eligible island server. If it is the current server, load the world locally (WorldService#load) and resolve. If it's a different server, send anIslandLoadRequestPacketand await a reply.The whole call is wrapped in a 15-second
orTimeout.
Cross-server requests travel over ISLAND_MANAGEMENT_CHANNEL (skyblock.island.management), registered in ASPacketRegistry:
Packet | Registry id | Purpose |
|---|---|---|
|
| Requests that a target server load an island's world (payload: island id, target server id). Sent with |
|
| Reply carrying a |
|
| Broadcast on delete; tells whichever server currently hosts the island to drop the world without saving (see Delete). |
IslandService's constructor registers the receiving side of ISLAND_MANAGEMENT_CHANNEL: an IslandLoadRequestPacket replies success = true immediately if the world is already loaded, otherwise loads it and replies with the outcome; an IslandDeletePacket unloads the world locally without saving if it's currently hosted on this server (the sending server is echo-suppressed, so it handles the local unload itself in WorldService#delete).
Cobblestone generators
A generator is a weighted block table an island's cobblestone/basalt generation re-rolls into. Generators live under plugins/AstralSkyblock/generators/*.yml, loaded by GeneratorService#load().
blocks maps a Material to a relative weight; non-block materials are silently filtered out when the table is built. GeneratorConfiguration#randomBlock() lazily builds a RandomCollection<BlockData> from the map (cumulative-weight binary search) and returns a weighted-random pick.
GeneratorService methods:
Method | Behavior |
|---|---|
| Loads |
| Looks up the generator whose id matches |
|
|
GeneratorListener registers only when generators.enabled is true in config.yml (independent of the island-server check). On BlockFormEvent, if the newly-formed block in an island world is COBBLESTONE or BASALT, it's replaced with a random block from generators().defaultGenerator()'s table and the event is cancelled so the vanilla block never actually forms. A second handler for BlockFromToEvent (lava-flow-based generation) exists in source but is currently a no-op stub pending a fix.
Per-island generator selection is not implemented in this version: GeneratorListener always uses the single network-wide defaultGenerator(), regardless of the island's GENERATOR upgrade level — that upgrade type exists in the enum but nothing in source currently reads it.
Island level & block values
block-values.yml assigns a point value to blocks and spawner types:
Each key is either a bare Material name, or MOB_SPAWNER:<ENTITY> which matches a placed spawner whose CreatureSpawner#getSpawnedType() equals <ENTITY> (case-insensitive). BlockValueConfiguration#blocks() compiles this into a Map<Predicate<Block>, Integer>.
Today this table backs exactly one consumer: IslandPermissionsListener#isValuable. On BlockBreakEvent, a block matched by any predicate in block-values.yml is gated behind IslandPermission.VALUABLE_BREAK instead of the plain BREAK permission (spawners specifically use SPAWNER_BREAK) — see Roles, Permissions & Settings.
Island#level is a persisted int column (islands.level), exposed as island.level() and via the level case of the island placeholder namespace — e.g. main.yml renders it as %parameters_island_level% ("Niveau : ...") since island-main is opened with the island passed in as the island menu parameter. It is written once at creation (always 0) and there is no code path in this version that recalculates it from block-values.yml or anything else afterward — the point values above currently only drive the VALUABLE_BREAK permission gate, not an island-level score.
Upgrades
UpgradeType enumerates nine purchasable tracks:
|
|---|
|
|
|
|
|
|
|
|
|
Each type's price/value tiers are defined in upgrades/<type>.yml, loaded by UpgradeService#load() into an IslandUpgrade blueprint (type, levels: Map<Integer, Level>):
| YAML key | Type | Notes |
|---|---|---|---|
|
| int | Tier number; also the map key. |
|
| double | Cost to reach this tier. |
|
| String | Currency id charged. |
|
|
| Display name for the currency. |
|
| double | The tier's effective value, read via |
|
|
| Actions listed for the tier; not currently executed anywhere in source (see caveat below). |
UpgradeService (upgrades()) methods:
Method | Behavior |
|---|---|
| Loads |
|
|
|
|
|
|
| Persists the new level, rebuilds the island's cached upgrade snapshot ( |
|
|
Island GUIs
Island management is almost entirely menu-driven (see Commands for the handful of ACF subcommands). Every screen is a menus/*.yml file with an explicit id, opened via [open-menu] <id> action chains:
Menu id | File | Purpose |
|---|---|---|
|
| Island hub — teleport shortcut, and links to members/roles/settings/coops/bans, each gated by a |
|
| Blueprint picker; each icon's click action includes |
|
| Toggles |
|
| Role list / role management entry point. |
|
| Per-role |
|
| Member roster. |
|
| Per-member action menu (kick/promote/demote/transfer/ban entry points). |
|
| Assigns a member's role. |
|
| Co-op roster. |
|
| Banned-player roster. |
|
| Yes/No confirmation step, opened from |
|
| Shipped but unreferenced — no menu or command in this build opens either one. |
Persistence & cross-server sync
IslandRepository extends the shared UUIDSyncedRepository<Island>/SyncedRepository base, a three-tier cache: L1 a local Caffeine cache per server, L2 a shared Redis cache, L3 MySQL (the source of truth). A write (save/create) goes to L3, then L1+L2, and only then publishes an update — so a server reacting to the notification never reads a stale L2 value. A delete removes from L3, invalidates L1+L2, and publishes an invalidation. Other servers' subscribers call invalidateLocally/cache.refresh on receipt (L1 only — L2 is already authoritative since it's shared).
Constant | Value |
|---|---|
|
|
|
|
|
|
|
|
IslandRepository#cascade (its postLoad hook) hydrates every transient relationship on a cache miss and on startup warmup — roles, members (each resolved to its role), owner, coops, settings, and upgrade levels — so a cached Island handed back from findCachedById/findById is always fully populated. IslandService#warmup() pages through every island 500 at a time (ASConstants.ISLAND_WARMUP_PAGE_SIZE), cascading and caching each one locally before the plugin finishes enabling; a warmup failure is logged but does not abort startup (islands missing from L1 are lazily loaded on first access instead). IslandService#refreshRelationships and #refreshUpgrades re-run a narrower slice of this cascade after a membership/role or upgrade write.
Events
Event | Fields | Fired |
|---|---|---|
|
| After |
|
| After |
|
| From |
|
| From |
All four extend Bukkit's Event with fluent accessors (event.island(), not getIsland()) per the plugin's lombok.accessors.fluent = true config. IslandCreateEvent and IslandDeletedEvent additionally derive their async flag from the calling thread at construction time (super(!Bukkit.isPrimaryThread())) rather than hardcoding it — listeners should not assume synchronous execution. See Developer API for the full event catalogue and listening examples.
See also
Overview — services, island-server vs lobby-server split, requirements.
Configuration —
config.yml,loader.ymland the rest of the shipped configuration surface.Commands —
/is create|delete|go|reloadsyntax and permissions.Roles, Permissions & Settings —
IslandPermission/IslandSettingscatalogues, includingVALUABLE_BREAK/SPAWNER_BREAK.Members, Co-ops & Invitations — membership, co-op and invitation flows.
Developer API — events, actions, and service access for other plugins.
Placeholders —
%skyblock_*%and per-model placeholder namespaces.