Skip to content

Event System Tasks ​

Event subscription, cancellation, and completion notifications.


Overview ​

The Yeow event system registers listeners with event.subscribe. Registration has two modes: observe (mode: "watch", read-only, not awaited by the runtime) and intercept (default: writable, released with event.complete).

event.subscribe ​

Subscribes to a game event.

  • Request: { "pluginName": "<name>", "eventType": "<eventType>", "callbackId": "<cbId>", "mode": "watch"? }
  • Return: true
FieldRequiredDescription
pluginNameYesOwning plugin name
eventTypeYesEvent name (camelCase, e.g. playerJoin), see the event name table
callbackIdYesEvent handler callback ID (must be registered with persistent: true)
modeNo"watch" = observer (read-only, not awaited); omitted = intercept (writable, waits for event.complete)

Trigger timing: When a game event occurs, the runtime delivers event data to callbackId (via the cb channel, with r being the event data object). The data includes the _cancellable boolean field marking whether it can be cancelled. Intercept deliveries additionally carry _eventId (a unique id per dispatch, see below) and a top-level eventId with the same value; observer deliveries do not generate _eventId.

event.unsubscribe ​

Unsubscribes from an event.

  • Request: { "pluginName": "<name>", "eventType": "<eventType>" } | { "pluginName": "<name>", "callbackId": "<cbId>" }
  • Return: true

The implementation should also remove the corresponding event listener, to avoid events continuing to be sent to JS callbacks that no longer respond.

Observer mode (mode: "watch") ​

Observers serve read-only use cases (logging, statistics, forwarding). The runtime delivers the event data and continues immediately: it registers no pending wait, generates no _eventId, and applies no write-back; an observer neither needs to nor can send event.complete. Because it does not participate in waiting, an observer neither extends the event's stay on the main thread nor triggers event.timeout.

event.complete ​

Intercept handler completion signal — the plugin informs the runtime that event handling has finished and subsequent steps can be scheduled. This task has no meaning for observers.

  • Request: { "eventId": "<eventId>", "mods": { "cancelled": <bool> } }
  • Return: true
FieldRequiredDescription
eventIdYesThe _eventId carried when the runtime delivers event data (top-level eventId), returned as-is — the unique id per dispatch
modsNoEvent modification object. When omitted or null, treated as {}
mods.cancelledYes*If true, the runtime must cancel the event (when mods is present)

mods can carry arbitrary write-back fields, and the runtime applies the commonly stable fields according to the event type (for each event's writable fields, see the fields marked "writable" in the Event Data Specification; unmarked fields are ignored). Besides cancelled, the following are currently supported: joinMessage / quitMessage / message (chat, command) / format / to / respawnLocation / deathMessage / newFoodLevel / damage / amount / target / clickedItem / cursorItem / serverPing's motd / maxPlayers / numPlayers / icon.


Event Handling Flow ​

1. A game event triggers
2. The runtime finds the plugins subscribed to this event and their callbackIds (observers and interceptors are handled separately)
3. Interceptors: event data is delivered through the cb channel → $hm(t="cb", p=callbackId, r={...event data, _cancellable:bool, _eventId});
   observers: the same data is delivered (without _eventId) and not awaited
4. The JS-side event handler executes
5. The interceptor sends event.complete (containing eventId) → received by the runtime
6. The runtime matches this dispatch by eventId and applies mods (e.g. cancelled)

Multiple Plugin Concurrency ​

When multiple plugins subscribe to the same event, the implementation can choose:

  • Serial: sends the event to each plugin one by one, waiting for one plugin to complete (event.complete) before sending to the next
  • Concurrent: sends to all subscribed plugins at once, waiting for the slowest plugin to finish

In both modes, the merge strategy for each plugin's modification of cancelled is: if any plugin sets it to cancelled, it is cancelled.