Skip to main content
A plugin written for one server makes three assumptions that stop being true here: that every online player is on this server, that a location can be read and written, and that a teleport is a teleport. ShardService is what replaces each of them.
Depend on atlas in your paper-plugin.yml so it loads first. Everything on the interface is safe from the main thread unless documented otherwise, and futures complete on the main thread.

The checklist

Never Player#teleport across a region

Use transfer. A raw teleport is intercepted and rerouted, but the event is cancelled, so your code is told it failed.

Never assume a player is here

Use locate and summon for anyone who might be on another shard.

Never write a block you do not own

Check isOwned, or use askAt and the scheduler to have the owner do it.

Always skip ghosts

isGhost before anything with a side effect. A ghost is a puppet of an entity living elsewhere.

Where a player is

locate asks the shard that owns them, so the answer is live rather than as old as their last save. The stored record is up to a minute stale, which is the wrong answer for anyone walking. onlinePlayers() lists everyone in the mesh.
A NetworkPlayer carries coordinates and a world name rather than a Location, because a location needs a world object and a shard only has its own.

Moving a player

The future completes when the handoff commits, which is before the player arrives. They are still standing here and about to disconnect; a few hundred milliseconds later they exist on another server with a different player object, and everything about them is overwritten from the snapshot. So this does not work:
Anything that must happen where they land is a named action, registered on every shard at enable, because a lambda cannot travel.
For a player who is not on this server, summon finds whoever holds them and has that shard do it.

Working on ground you do not own

askAt runs a registered query on whichever shard owns a location and gives you the answer. ShardScheduler.runAt is the same thing without a reply, for “make this happen over there”.
safeLanding is the one everybody needs: somewhere near a point that a player can stand without being hurt by it, resolved by the shard that owns the ground. It is allowed to fail, and a caller that teleports anyway is the bug it exists to prevent.

The sub-services

Everything registered must be unregistered on disable: arrival actions, queries, data adapters and subscriptions. A handler left behind holds your classloader and goes on answering for a plugin that no longer exists.