Listen for lifecycle events
Reverse exposes Bukkit events so integrations can observe lifecycle without patching command handlers. Register listeners through Bukkit’s PluginManager using your integration plugin as the owner.Choose the event for the outcome you need. Use
RecordingStopEvent for capture ending, RecordingSavedEvent for persisted data, and PlaybackStopEvent for playback cleanup finishing. Catalogue changes arrive as ReplayDeletedEvent/ReplayMetadataUpdatedEvent, and watch outcomes as DispatchResultEvent.Recording events
Imports usecom.marioded.reverse.api.recorder.event.
A saved replay can still be degraded. Check
isDegraded()/getTerrainFailure() instead of assuming every RecordingSavedEvent has usable terrain. getTickCount() counts occupied event ticks, not wall-clock seconds or a guarantee that the timeline is dense.
These persistence events also cover clip exports: their replay ID is the clip’s fresh ID. Exporting a clip leaves the source recording active and does not emit RecordingStopEvent for it.
Save notifications are dispatched directly on the completion thread. Schedule any Bukkit UI, player lookup, or world access onto the main thread. Events may be attempted during shutdown; only listeners still registered receive them. Keep persistent match bookkeeping in the save stage or your own durable state if receiving an event during teardown is not sufficient.
Playback events
Imports usecom.marioded.reverse.api.player.event.
Stop is a post-cleanup notification. The session is no longer in the service’s active registry. Its viewer snapshot contains those present when stopping began; earlier leavers are not included, and handles can already be offline.
session.isFinished() distinguishes reaching the timeline end from an early stop; there is no separate public stop-reason enum.
Catalogue events
Imports usecom.marioded.reverse.api.catalog.event.
The runtime’s
MemoryCachedStorageProvider implements both storage and catalogue access and emits these notifications around its catalogue delegate. Use the facade’s catalogue; calling a raw backend directly, or calling the one-argument StorageProvider.delete(replayId), does not emit these catalogue events.
- A successful deletion has
isDeleted() == true. A missing/forbidden deletion hasfalseand no failure. An exceptionally completed deletion stage hasfalseand its cause ingetFailure(); the returned stage remains exceptional. - A metadata result of
UPDATEDconfirms the replacement, including when the text is unchanged.MISSING_OR_FORBIDDENalso emits an event, but its text fields are the requested values, not evidence that anything was written. Exceptional metadata completion emits no metadata event; handle the returned stage to observe backend failures. - A synchronous throw before the delegate returns a stage does not reach either completion hook. Invalid arguments are not guaranteed to produce a notification.
isAsynchronous() according to its constructing thread; schedule Bukkit-facing work yourself.
Dispatch events
Imports usecom.marioded.reverse.api.lobby.event.
getResult() is the DispatchService.Result: SUCCESS, NOT_AUTHORIZED, NO_REPLAYER, or TRANSFER_FAILED. For valid watch(viewer, replayId) calls, this includes immediate offline/overlapping-request refusals. An immediate refusal fires on the calling thread and has no recorded cause. It does not release another request that is already pending.
For an accepted request, the runtime releases its pending marker before emitting the event on the request’s completion thread. There is no main-thread notification guarantee. An exceptional request is reported as TRANSFER_FAILED with getFailure() carrying the cause, while its returned stage remains exceptional. Some downstream transfer/availability failures are instead logged and converted to a normal TRANSFER_FAILED result, so a null event failure does not prove there was no backend error. Invalid/null arguments are not guaranteed an event. The viewer handle may already be offline.
SUCCESS means the pending selection was stored and a proxy transfer message was sent; it does not confirm arrival or playback start. Observe PlaybackStartEvent on the destination for that outcome.
Listener pattern
Let’s react when a replay finishes saving. In a JavaPlugin that implements BukkitListener, register the listener during enable:
isAsynchronous() according to the constructing thread. Ordinary player runtime operations should occur on the main thread, but use the event’s actual context instead of assuming every event delivered through Bukkit is synchronous.
Notifications, not cancellation hooks
These events do not implement BukkitCancellable. They are not a supported way to veto a recording save, deny a viewer after playback starts, or rewrite seek behavior. Enforce access before starting/dispatching, and use service operations for intentional lifecycle changes.
Avoid an unconditional listener that responds to a pause event by immediately setting the opposite pause state: reentrant state changes can create event loops. Likewise, do not retain mutable playback contexts as asynchronous work items.
