Events and timers
This page is part of Writing Lua scripts. The functions are listed in the reference:
events and timer.
Events
Section titled “Events”Save this listener as scripts/common/character_log.lua:
local character_log = {}
character_log.handle = events.on("character_created", function(e) log.info("New character {Name}", e.name)end)
return character_logAppend require("common.character_log") to the existing scripts/init.lua, keeping
its other code. Start the server or enter script reload init.lua in the server
console. Create a new character from the client: the console log should show
New character followed by its name. Logging in with an existing character does
not raise character_created.
Keep the returned module table if you need to unsubscribe later. To remove the listener deliberately, run this from code that requires the same module:
local character_log = require("common.character_log")events.off(character_log.handle) -- returns false when the handle is unknownThe second block is cleanup; do not append it to the first test, or the listener will be removed before a character can be created.
- Each handler runs on the game loop as a coroutine, so it may call
wait(). - Handlers of one event run in the order they subscribed. Subscribing or unsubscribing inside a handler takes effect from the next event.
- Every handler receives its own table; changing it does not affect other handlers.
- Events are notifications: a handler cannot cancel or change what happened. An error in a handler is reported like any script error, and the other handlers still run.
- Subscriptions belong to the file that made them. Reloading or invalidating the file removes them, like its timers; stopping the engine removes all of them.
- An unknown event name raises an error in
events.on. The generateddefinitions.lualists the valid names as theEventNamealias, so editors complete them.
Available events
Section titled “Available events”| Event | Fields |
|---|---|
character_created |
serial, account_id, name, race and gender (numbers of the race and the gender; scripts get no enum tables for them), map, x, y, z. Raised after a new character and its starting items are saved. |
character_deletion_requested |
serial, account_id, name. Raised after a player asks to delete a character; it stays restorable until removed. |
character_entered_world |
serial, account_id, name, map, x, y, z. Raised after a character entered the world and the client’s login completed. |
player_say |
serial, name, text, type. Raised after a player’s character said something and the players and NPCs around heard it; text is what they heard and type how it was said, a number such as SpeechType.Yell. A command (text starting with a dot) raises nothing. |
character_left_world |
serial, account_id, name, map, x, y, z. Raised after a character left the world because its session closed, once its save was attempted. |
player_region_changed |
serial, name, previous, current, map, x, y, z. Raised when a player’s character walks or is teleported from a region into another, or changes map; previous and current are the regions’ names, nil outside every region, and previous is also nil when the character just entered the world |
item_created |
serial, template (nil when the item has none). Raised when a new item entered the world: made by a script, a vendor, a spawn or a craft. What is loaded at startup or at a login, the rest of a split stack, the items .decorate places and the items a book attachment settles raise nothing. |
item_deleted |
serial, template. Raised when an item was deleted: decay, a merge into a stack, item.delete, the gear of a removed NPC. Logging out raises nothing. |
mobile_created |
serial, template. Raised when an NPC was spawned, also a pet that came out of a stable, a ridden pet that was dismounted or a bonded pet raised from its corpse: the creature has a new serial each time. The NPCs loaded at startup raise nothing, and an ethereal statuette makes no creature. |
mobile_deleted |
serial, template. Raised when an NPC left the world: after its death, .remove, a pet that went into a stable, or a pet that was mounted and became the mount item its rider wears. A pet that comes back has a new serial. |
mobile_died |
serial, killer, corpse (0 for none). Raised when a player or an NPC died; a human body raises it when its fall is over. |
pet_tamed |
serial (the pet), tamer. Raised when a creature became a player’s own, by pet.tame or .tame. |
craft_succeeded |
actor, craft (the id of the craft), item (the serial of what was made). Raised by the shared crafting script when a try succeeded; a failed try raises nothing. |
corpse_carved |
serial (the corpse), carver, items (how many items were made, inside the corpse or beside it, a stack counting as one). Raised when a knife took items off a corpse (carve.corpse); a carve that found nothing raises nothing. |
party_invite |
party_id, leader, invitee. Raised when a leader invited a player; party_id is the number of the party, kept while the server runs. |
party_joined |
party_id, member. Raised when an invited player accepted and joined the party. |
party_left |
party_id, member, reason (left, kicked or logout). Raised when a member is no longer in the party, also the one whose leaving ends it. |
party_disbanded |
party_id, reason (too_small when fewer than two members remained, emptied when the only member’s last invitation ended). Raised when a party is gone. |
The last twelve are notifications of the world, not of a character: the server publishes each one only while a script listens to it, so an event nobody subscribed to costs one lookup. Script hooks lists them next to the hooks and says what is not built.
Each delivery is one work item of the game loop. A mass fill with a listener on item_created, such as a spawn or a
script that makes thousands of items, posts one work item for each item; when the loop’s queue is full the events are
dropped, with a warning in the log.
Publishing an event from C#
Section titled “Publishing an event from C#”Hosts and plugins publish a bus event to Lua with an explicit registration, before the engine starts:
container.AddScriptEvent<MyEvent>( "my_event", e => new Dictionary<string, object?> { ["name"] = e.Name, ["amount"] = e.Amount });The name must be snake_case and unique, and each event type is published once. The mapping runs on the publishing thread and must only read the event. It may return strings, booleans, numbers, enums (sent as numbers) or null. A mapping that fails is logged, and the event is skipped for Lua only. Events published while no script is subscribed cost one lookup and are not queued.
Timers and wait
Section titled “Timers and wait”All timer delays and wait arguments are in seconds.
timer.after runs a function once, timer.every repeats it and timer.cancel stops either; wait parks
the coroutine that calls it.
Call wait from a scheduled coroutine, such as a timer callback, not at the top
level of init.lua or a required module. It needs a positive, finite delay that
fits the timer range. It yields the coroutine rather than blocking the thread.
Each repeating timer occurrence starts a coroutine: if one calls wait for longer
than the repeat interval, multiple suspended invocations can coexist. Cancelling
the timer prevents later starts; it does not cancel an already-started coroutine.
For sequences that must not overlap, use a one-shot callback that schedules its
next run only after its work finishes.
Made with ❤️ by Tom
