Events and handlers
Subscribe to events, write async handlers safely, look up every event the server and client raise, and emit events of your own.
Almost everything a resource does starts in an event handler. Subscribe with
Events.on; the arguments are
typed from the EventMap. If
an event is not in the lists below, the game does not raise it.
// serverEvents.on("playerSpawned", (player) => { Chat.sendToAll(`${player.nickname} rode into town.`);});Subscribe and unsubscribe
Section titled “Subscribe and unsubscribe”| Call | Does |
|---|---|
Events.on(name, fn) |
Subscribes. Returns a function that unsubscribes. |
Events.once(name, fn) |
Removes the handler before its first call. Returns nothing. |
Events.off(name, fn) |
Unsubscribes the same name and function. |
// serverconst stop = Events.on("playerChat", (player, text) => { console.log(`${player.nickname}: ${text}`);});stop();Handlers are removed when your resource stops; you never need to clean up there. See Resources.
Async handlers
Section titled “Async handlers”Handlers run in registration order, each up to its first await. The emitter
gets a promise that settles when all have; if any throws or rejects, it
rejects with an AggregateError. A synchronous throw is also logged with its
stack.
Code after an await runs later than the event, so re-check that a player is
still connected before acting on them. Some events need an answer in the
handler itself:
playerSpawning: callplayer.spawn(...)before anyawait, or the player lands on the level’s default start point.horseMounting,horseGearChanging,playerPickpocketStart,doorInteract: returnfalseto refuse. An async handler cannot refuse.playerConsuming,playerConsumptionEffects,playerPoisonAbsorbing: returnfalsesynchronously to refuse the serving, its native effects, or added pot poison respectively. See Food and potions.
On the client, questTrackingChanged, poiDiscovered and noclipChanged do
not await handler promises. Your async handler still runs; nothing waits.
Keep ids, not handles
Section titled “Keep ids, not handles”Handles are valid inside the handler. Teardown events (playerDisconnect,
npcDestroy and the other ...Destroy events) fire while the handle still
reads, so read a name there one last time. To use a subject later, keep its id
and look it up again: getById returns null once it is gone.
// serverconst inQueue = new Set<number>();Events.on("playerSpawned", (player) => inQueue.add(player.id));Events.on("playerDisconnect", (player) => inQueue.delete(player.id));
function announce(text: string): void { for (const id of inQueue) { const player = Player.getById(id); if (player) Chat.sendToPlayer(player, text); }}Server events
Section titled “Server events”An argument typed Player | null (noted as attacker?, player? and so on)
can be null: check it. The
EventMap reference has
exact types and every union value.
Players
Section titled “Players”Guides: Join and spawn, Health and stats, Buffs, Chat.
| Event | Arguments | Fires when |
|---|---|---|
playerConnect |
player |
Their body exists, still loading. ready is false. |
playerSpawning |
player |
Their body needs a place to stand. Answer synchronously. |
playerSpawned |
player |
They are standing in the loaded world. Give kit here. |
playerDisconnect |
player |
They are leaving; the handle still reads. |
playerHit |
player, attacker, hit |
A player hit or NPC melee hit is about to take health. Return false to refuse; change hit.damage to set the amount. |
playerDamage |
player, attacker?, amount, bodyPart?, reason |
Health came off them, after any playerHit ruling. |
playerInjured |
player, bodyPart |
A limb becomes injured. |
playerInjuryHealed |
player, bodyPart |
A limb injury is gone. |
playerDied |
player, killer?, reason |
A death is accepted. No automatic respawn. |
playerChat |
player, text |
They submit a plain chat line. |
playerCommand |
player, command, args |
A / line no built-in command claimed. |
playerBuffAdded |
player, buff, source |
An effect appears. source is "server" or "native". |
playerBuffRemoved |
player, buff, reason |
An effect leaves. reason is "server" or "expired". |
playerBuffBlocked |
player, buff |
The game tried a buff kind you Buffs.claimed. |
playerPickpocketStart |
thief, victim |
A Rob attempt begins. Return false to refuse. |
playerPickpocketed |
thief, victim, items |
A theft settled; items is what really moved. |
playerPickpocketCaught |
thief, victim |
The victim noticed. Nothing was taken. |
For playerHit, the attacker can be a player or NPC. The attacker/killer
in player damage and death events can also be null. Check the type before
using player-only properties. Lowering health from a script raises damage
with a null attacker; see Set and restore stats.
Horses and dogs
Section titled “Horses and dogs”| Event | Arguments | Fires when |
|---|---|---|
horseSpawn, horseDestroy |
horse |
A horse is created, or is being despawned. |
horseMounting |
horse, player |
A player climbs on. Return false to refuse. |
horseMount, horseDismount |
horse, player? |
A rider got on or off (including on death or disconnect). |
horseDamage |
horse, attacker?, amount, reason |
Health came off a horse. |
horseDeath |
horse, killer?, reason |
A horse dies. |
horseGearChanging |
horse, player?, gear |
A player changes gear. Return false to refuse. |
horseIntentDone |
horse, status |
A move or patrol leg reached, was blocked or failed. |
horseGearChanged |
horse, player?, gear |
Gear changed on every client. player is null for a script. |
dogSpawn, dogDestroy |
dog |
A dog is created, or is being despawned. |
dogOwnerChanged |
dog, player? |
A dog is handed over or left masterless. |
dogModeChanged |
dog, mode |
Its companion mode actually changes. |
Guides: Spawn NPCs, Move NPCs, NPC damage and death.
| Event | Arguments | Fires when |
|---|---|---|
npcSpawn, npcDestroy |
npc |
An NPC is spawned or adopted, or is being despawned. |
npcIntentDone |
npc, status |
An order ends: reached, blocked or failed. |
npcDamage |
npc, attacker?, amount |
Health came off, as agreed by the server. |
npcDeath |
npc, attacker? |
Its health runs out. The corpse stays. |
patrolWaypoint |
npc, info |
A waypoint was reached, blocked or failed. |
patrolFinished |
npc, info |
A patrol completed, failed or was cancelled. |
npcRevive |
npc |
A dead NPC is brought back. |
npcInteract |
npc, player |
A player presses use on an interactable NPC. |
npcSimulatorChange |
npc, player? |
The client running it changes. null means dormant. |
npcInventoryReady |
npc |
Initial server-owned stock is established. |
npcInventoryChanged |
npc, change |
Stock changes, including committed loot transfers. |
npcHarvested |
npc, player |
Animal harvesting completes. |
NPC damage and death attackers are Player | Npc | null.
Named patrol routes explains the
route ID, waypoint index and phase carried in patrol events.
World and objects
Section titled “World and objects”Guides: Time and weather, Doors and gates, Props, Particle effects, Markers, Items.
| Event | Arguments | Fires when |
|---|---|---|
worldResourcesReady |
none | All configured exports loaded. Check WorldResource.ready when starting later. |
worldDayChange |
day |
The clock crosses midnight. day is the new one. |
worldWeatherChange |
preset, previous, seconds |
A weather blend starts. |
doorInteract |
player, door, action, keySide |
A player works a door. Return false to refuse. |
propSpawn, propDestroy |
prop |
A prop is created, or is being despawned. |
vfxSpawn, vfxDestroy |
vfx |
An effect is placed, or is being stopped. |
markerPlace, markerRemove |
marker |
A marker is drawn, or is being removed. |
markerEnter, markerExit |
marker, player |
A player walks into or out of a trigger marker. |
groundItemSpawn, groundItemDestroy |
groundItem |
A stack is laid down, or is being removed. |
groundItemPickup |
groundItem, player? |
A pickup was granted. groundItemDestroy follows. |
Quests, dialogue and shops
Section titled “Quests, dialogue and shops”Guides: Quests, Dialogue, Shops.
| Event | Arguments | Fires when |
|---|---|---|
questTrackingChanged |
quest, player, tracked |
A player follows or unfollows a quest. Cannot be refused. |
dialogueChoice |
session, player, optionId |
A player picks an option. |
dialogueClosed |
session, player, reason |
A conversation ends. |
vendorTrade |
vendor, player, bought, sold, balance |
A deal has settled. |
vendorClosed |
vendor, player, reason |
A trading session ends. |
Resources and state
Section titled “Resources and state”| Event | Arguments | Fires when |
|---|---|---|
resourceStart |
resourceName |
Any resource’s scripts have run, just before it counts as running. See Resources. |
resourceStop |
resourceName |
Any resource is stopping, before cleanup. |
entityStateChange |
entity, key, value, previous |
A state bag key changes. See State bags. |
Client events
Section titled “Client events”| Event | Arguments | Fires when | Guide |
|---|---|---|---|
resourceStart, resourceStop |
resourceName |
As on the server. | Resources |
entityStateChange |
entity, key, value, previous |
A state write arrives. | State bags |
questTrackingChanged |
questKey, tracked |
This player follows or unfollows a quest. | Quests |
vendorOpened |
session, npc |
The trade screen actually comes up. | Shops |
vendorClosed |
session, reason |
That screen goes away. | Shops |
poiDiscovered |
poiId |
A silent POI discovery is accepted. | Map and blips |
mapOpened, mapClosed |
none | The map screen opens or closes. | Map and blips |
mapWaypointSet |
position, mapId, moved |
The player drops or moves their map marker. | Map and blips |
mapWaypointCleared |
position, mapId |
The player removes it. | Map and blips |
followStarted |
target |
A queued native follow entered. | Following |
followStopped |
target, reason |
A follow stopped or failed to enter. | Following |
monologueSuppressed |
text, textKey |
A local character remark was suppressed. | HUD |
noclipChanged |
active, reason |
Free camera starts or ends. | Camera and noclip |
browserCreated, browserLoadingStart, browserDocumentReady, browserLoadingFailed |
event |
A web view is created, loads, is ready for Web.emit, or fails. |
HTML pages |
browserNavigate, browserPopup, browserOriginChange, browserResourceBlocked |
event |
A view navigates, blocks a popup, changes origin, or blocks a request. | HTML pages |
browserCursorChange, browserTooltip, browserInputFocusChange, browserConsoleMessage |
event |
A view asks for a cursor or tooltip, gains or loses text focus, or logs. | Page data bridge |
Chat lines from the server arrive through a reserved chatMessage event that
the declarations mention but do not type; see
Chat and /commands.
Inventory, consumption and books
Section titled “Inventory, consumption and books”| Side | Event | Arguments and purpose |
|---|---|---|
| Server | playerInventoryReady |
player: the initial inventory is displayed. |
| Server | playerInventoryChanged |
player, change: a committed inventory change. |
| Server | playerCustomItemUse |
player, row, revision: a validated custom use request; no automatic consumption. |
| Server | playerConsuming |
player, consumption: allow or refuse the serving. |
| Server | playerConsumptionEffects |
player, consumption: keep or suppress its native effects. |
| Server | playerPoisonAbsorbing |
player, consumption, buff: allow or block added pot poison. |
| Server | playerConsumed |
player, consumption: successful native use was confirmed. |
| Server | playerStatsChanged |
player, changes: stat changes grouped by tick, with previous and current values. |
| Client | bookOpened |
id: the requested book reached the player’s hands. |
| Client | bookClosed |
id, reason: reading ended or opening failed; promises are not awaited. |
See Inventories, Custom items, Food and potions and Books and letters for timing and failure cases.
Emit your own events
Section titled “Emit your own events”Any name that is not native is a custom event on the same bus. Every resource shares it, so prefix names with your resource name.
// serverEvents.on("my-mode:round.end", (winner) => { if (typeof winner !== "string") return; // custom arguments are `unknown` Chat.sendToAll(`${winner} wins the round.`);});
Events.emit("my-mode:round.end", "Ravens").catch((error) => { console.error(`a round.end handler failed: ${String(error)}`);});Events.emit returns the promise described in Async handlers.
await it or attach a .catch: an unhandled rejection triggers your
errorBehavior.
| Call | Reaches |
|---|---|
Events.emit(name, ...args) |
Every resource’s Events.on handlers. |
Events.emitTo(resourceName, name, ...args) |
Only that resource’s Events.on handlers. |
Events.emitLocal(name, ...args) |
Only your own Events.onLocal handlers. onLocal has no off. |
Events.listenerCount(name) |
Not an emit: counts on and once handlers across resources. |
Events between server and client use Events.onClient on the server, a
separate table a client cannot use to reach native or resource events. See
Send data between server and client.
Related
Section titled “Related”- Server vs client authority: what an event confirms
- Send data between server and client: events across the wire
- Resource manifest and lifecycle:
resourceStart,resourceStopand errors - EventMap reference: exact types and notes