Skip to main content

Protocol adapters

Advanced API reference · Most integrations do not need these contracts This page explains the adapter contracts available in the public API. Most game plugins need only recording, playback, and custom recordables.
An adapter translates between Minecraft’s protocol and Reverse’s shared data. It is not the entry point for an ordinary Watch button or minigame recording hook.

Why there are two adapters

ReverseRecorderAdapter turns platform/protocol observations into portable capture data. ReversePlayerAdapter turns portable replay data into client-visible playback. They live in separate role APIs because recording and viewing run independently. Keep custom recordables in terms of the shared API data types. They should not require a particular packet library to decode their saved payload.

Recorder adapter responsibilities

A capture adapter:
  • Installs entity metadata and game profile providers during initialization.
  • Subscribes to wire events and routes observations to RecorderService.
  • Converts Bukkit items, block states, entity types, and chunk snapshots into shared payloads.
  • Reports protocol capabilities needed by compatibility-sensitive listeners.
  • Resolves the player’s current world directly; no mirrored player/world routing map is required.
  • Unsubscribes safely on shutdown, including partial-initialization paths.
Block naming must preserve complete state properties, not just a material name. An unresolved state can return null; do not replace it with an arbitrary valid block and silently corrupt the replay.

Entity metadata provider

EntityMetadataProvider.snapshot(entity, entityTypeName) captures a protocol-neutral list of typed metadata entries on the main server thread. The metadata service holds the installed provider and rejects retrieval before installation. Metadata entries represent typed fields, not a complete Bukkit entity serialization. Packet metadata often carries only changed fields; the durable state layer merges those partial updates. A backend must not interpret one partial packet as replacement for the entire entity metadata set.

Game profile provider

GameProfileProvider extracts the shared profile model from a Bukkit player for recorded player entities. ProfileProviderService holds that selected provider and throws if queried before one is installed. Player profiles are captured data, not live Bukkit Player references suitable for persistence. Keep profile properties with the recorded model rather than fetching an unrelated current player at playback time.

Player adapter responsibilities

A playback adapter registers its renderer implementations, sends/unloads chunks, sends action-bar components, and updates the client experience display. The renderer service dispatches by recordable type; the PlaybackContext supplies session-specific entity mappings and viewers. The experience progress shown by the replay is packet-driven display. It should not become the viewer’s actual saved experience state. Clearing temporary HUD output on session end matters because clients retain the last value they received. An adapter does not authorize catalog selections or own match lifecycle. Those decisions stay in the application/services above it.

Capability handling

The SPI exposes a protocol version and named capability helpers such as swap-hand support and, on capture, block-data support. Use those helpers rather than depending on a packet library’s version enums. Legacy support needs more than a number comparison: loading a listener whose parameter class does not exist can fail before any handler runs. Keep modern-only listener registration behind the appropriate capability path, and isolate references to newer Bukkit APIs.

Adding another backend

Both player and recorder select their adapter by id: adapter: <id> in the role’s config.yml (default packetevents). The composition root builds a FactoryRegistry from the adapter factories this build ships and resolves the configured id; an unknown id fails startup with the list of known ids — there is no silent fallback to another protocol. Adding a protocol is an in-tree change, not a runtime plugin hook: implement ReverseRecorderAdapterFactory in protocol-<name>-recorder and ReversePlayerAdapterFactory in protocol-<name>-player, with shared vocabulary in protocol-<name> only when needed. Give the factories an id and explicitly register each in its role’s composition root. The registry is a one-shot id lookup over that fixed set — there is no ServiceLoader, Bukkit-services scan, or external injection of adapter ids. ServicesManager lookup only obtains the active ReverseRecorder/ReversePlayer facade. There is no hot-swap: the adapter is consumed while the plugin enables (renderers, packet listeners, and services are constructed from it), so changing the id requires a restart.

Metadata and profile providers

ReverseRecorder exposes the installed provider holders through getMetadataProviderService() and getProfileProviderService(). Replacing a provider takes effect on the next capture, and packet listeners consult providers off the main thread, so implementations must be thread-safe. Do not use reflection to bypass these holders and treat a mid-recording swap as an ordinary supported integration path.

Validation checklist

Verify the supported server floor, missing optional APIs, entity ID remapping, partial metadata/equipment, movement units, block properties, chunk vertical bounds, callback threading, startup failure cleanup, and a save/load/play cycle between independent machines. Compiling against a new Bukkit API alone is not evidence that an older runtime contains those methods.