Skip to main content

Ghost

Draw players from other servers of your network as client-side ghosts, with a per-player switch.

Privatev0.1Social
RequiresTypeWriter0.9.0-beta-177 (BTC build)MySQL (TypeWriter extension)Redis or ValkeyPacketEventsMiniPlaceholdersUiCore (TypeWriter extension)optional

Built for the BornToCraft network. This extension is documented for reference and is not distributed.

Overview​

On a network of several backend servers, two players standing in the same map on different servers cannot see each other. Ghost fixes that visually: each server publishes the position, rotation and skin of its players through Redis, and the other servers draw them as player-shaped ghosts using packets. Nothing is added to the world, and a ghost has no server-side entity, so it cannot be hit, pushed or targeted.

Every player decides whether they see ghosts. The choice is stored in a TypeWriter fact, so it survives restarts and follows the player from server to server. Ghosts only appear between servers that share the same redis.group-id in the MySQL configuration, and a server never draws its own players as ghosts.

Features​

  • Cross-server ghosts — players of other backend servers appear with their skin, position and rotation.
  • Packet-only rendering — built on PacketEvents, no server-side entities are created.
  • Folia-safe — each capture and each render runs on the scheduler of the player concerned.
  • Visibility switch — /ghost toggle, a TypeWriter action or a menu button turns ghosts on and off per player.
  • Persistent choice — the preference is a persistable fact shared across servers.
  • Server tag — a configurable suffix such as [survival-2] shows where a ghost comes from.
  • Limits — view distance, maximum ghosts per viewer, update rate and world filtering keep the cost bounded.
  • Nameplates — if UiCore is installed, ghosts get a nameplate above their head.
  • Placeholders — visibility and ghost count for MiniMessage text.

Installation​

  1. Install PacketEvents on every server.
  2. Install the MySQL extension and enable Redis (or Valkey) on every server. Give each server a unique redis.server-name and the same redis.group-id. Ghost does not have its own Redis settings.
  3. Put the Ghost JAR in plugins/Typewriter/extensions/ and restart. The file plugins/Typewriter/ghost_player/config.yml is created on first start.
  4. Grant the permission typewriter.ghost.use to the players who may use /ghost. Without a permissions plugin, only operators can.
  5. Optional: install UiCore to draw nameplates above ghosts.

Getting started​

Let players switch ghosts from a menu or a quest NPC:

  1. In the TypeWriter web panel, open a page and add a Ghost Visibility Fact fact entry. Keep its default id, ghost_visibility_fact: the extension reads and writes the fact by this id.
  2. Add a Ghost Toggle Visibility action on a sequence or a menu item. Fill showMessage and hideMessage with MiniMessage text, or leave them empty for the default messages.
  3. Optionally add a criteria on the Ghost Visibility fact (value 1 means visible, 0 hidden) to show a different icon in a menu.
  4. Publish and join two servers of the same group. Each player sees the other as a ghost with the server suffix.
No visible ghosts on a single server

A server ignores its own broadcasts. To test, start two servers with the same redis.group-id and different redis.server-name, and stand in worlds that are allowed to match (see below).

How it works​

Each server publishes a snapshot of every local player at the configured rate, plus a heartbeat so idle players stay alive. A ghost disappears when its owner leaves, or when no update arrives within timeout-millis. Each message carries a session id and a sequence number, so late or duplicated messages are ignored.

A ghost is drawn for a viewer when the viewer has ghosts enabled, the worlds are compatible, the ghost is within view-distance and the viewer has fewer than max-ghosts-per-viewer ghosts, the nearest first.

Configuration​

The file is plugins/Typewriter/ghost_player/config.yml. Its schema-version must be 1 or 2.

schema-version: 2

settings:
update-rate: 4
server-suffix: " <gray>[%server%]</gray>"
team-prefix: "Ghosts"
view-distance: 64.0
max-ghosts-per-viewer: 128
timeout-millis: 10000
allowed-worlds:
- "world"
- "dungeon_"
- "hub"

messages:
no-permission: "<red>You do not have permission to use ghost visibility."
players-only: "<red>This command can only be used by a player."
enabled: "<green>Ghost visibility enabled."
disabled: "<yellow>Ghost visibility disabled."
usage: "<gray>Usage: <white>/ghost toggle"

command:
permission: "typewriter.ghost.use"
KeyMeaning
settings.update-rateTicks between position captures. Clamped to 1 to 20.
settings.server-suffixMiniMessage text added after the ghost's name. %server% becomes the origin server name.
settings.team-prefixPrefix of the scoreboard team shown for each origin server.
settings.view-distanceMaximum distance in blocks, from 0 to 256. 0 disables the distance limit.
settings.max-ghosts-per-viewerGhosts drawn per viewer, from 0 to 2000. 0 shows none.
settings.timeout-millisDelay without update before a ghost is removed, from 2000 to 120000.
settings.allowed-worldsWorld name patterns that count as the same map across servers. Up to 64 entries.
messages.*MiniMessage texts used by /ghost.
command.permissionPermission required by /ghost.

Worlds. A ghost is visible when the viewer and the ghost are in worlds with the same name, or when both world names contain the same pattern from allowed-worlds (for example dungeon_ matches dungeon_1 and dungeon_2). Distance can only be measured inside one world, so with a view-distance above 0, ghosts from a differently named world are hidden. Set view-distance to 0 if you rely on pattern matching.

Redis connection, server name and group id are read from the MySQL configuration. The file is read once at startup: restart the server after editing it.

Commands​

CommandDescription
/ghost toggleTurn ghost visibility on or off for yourself.

Aliases: /ghosts, /ghostplayer. Players only. The permission is command.permission, typewriter.ghost.use by default.

Permissions​

PermissionGrants
typewriter.ghost.use/ghost toggle. The name can be changed with command.permission. The permission is not registered with a default value, so it needs to be granted.

The Ghost Toggle Visibility action does not check this permission.

Placeholders​

MiniPlaceholders tags for MiniMessage text. PlaceholderAPI is not supported.

PlaceholderReturns
<ghost_visibility>true when the player sees ghosts, false otherwise.
<ghost_visible>1 when the player sees ghosts, 0 otherwise.
<ghost_count>Number of remote ghosts currently known to this server.

Entries​

2 entries, generated from the extension source.

Actions1
Facts1

Troubleshooting​

No ghost appears. Check, in this order: both servers use the same non-empty redis.group-id and different redis.server-name values, Redis is connected (the log says "BTC RedisService is not connected; Ghost updates will remain inactive" otherwise), the viewer has ghosts enabled, and the two players are in compatible worlds (same world name, or a shared allowed-worlds pattern with view-distance: 0).

Players of the same server never show as ghosts. This is expected: a server ignores messages it published itself.

The extension fails to start with "Unsupported Ghost custom config schema". schema-version in ghost_player/config.yml is outside 1..2. Set it to 2.

The extension fails with a server name error. redis.server-name in the MySQL configuration must contain only letters, digits, ., _ or -, up to 64 characters.

/ghost says you do not have permission. The permission has no default. Grant typewriter.ghost.use (or the permission you set in command.permission) with your permissions plugin.

Ghosts vanish after a few seconds. No update arrived within timeout-millis. Check Redis latency, and that the origin server is running Ghost with the same redis.group-id.

Ghosts have no nameplate. Nameplates come from UiCore. Install it on the viewing server. Without it only the server suffix in the name is shown.

  • MySQL — owns the Redis connection, server name and group id used by Ghost.
  • UiCore — draws nameplates above ghosts when installed.