Events, commands and timers
A resource reacts to the server in three ways: event handlers for what happens in the game, command handlers for what players and the operator type, and timers for what is due later. All three run on the server's main loop, one callback at a time (Threading and timing), and are revoked when the resource stops.
Events
Subscribing
dlmp.add_event_handler(event, element, fn, opts) -- true | nil, err
dlmp.remove_event_handler(event, element, fn) -- true | nil, err| Argument | Contract |
|---|---|
event | A built-in event name. The names and the arguments of each event are listed at dlmp.add_event_handler in dlmp.lua. An unknown name returns nil, err. Resources cannot declare events of their own; they talk to each other through exports. |
element | An Element: dlmp.root, dlmp.resource, dlmp.resource_root, or a dlmp.source kept from an earlier event. Any other value, a Player or Vehicle handle included, raises an error. |
fn | The handler. The same event, element and fn registered twice by one resource returns nil, err, and so does removing a handler that is not registered. |
opts.propagate | true by default: the handler also fires for events whose source lies below element in the element tree. false: only for events sourced at element itself. |
opts.priority | "high", "normal" (the default) or "low". Any other string raises an error. |
Handlers can be registered at load time (the top level of a script or module) and from any callback.
Event sources
Every event has a source element; the source decides which handlers hear it.
| Source | Events |
|---|---|
the starting or stopping resource's resource_root | resource_start, resource_stop |
| the root | player_connecting (no player exists yet), player_auth (only the auth resource hears it) |
| the player | every other player_* event |
| the pickup | pickup_interact |
| the vehicle | vehicle_* events; trailer_attach, trailer_detach at the trailer; winch_attach, winch_detach at the lifted vehicle |
| the checkpoint | checkpoint_enter, checkpoint_leave |
| the colshape | colshape_enter, colshape_leave |
| the object | object_move_end |
the edited element, or the editing resource's resource_root | entity_select, entity_edit, edit_end of an edit session |
Players hang under the root, created elements under their resource's dlmp.resource_root. So dlmp.root hears everything, dlmp.resource_root hears the events of the resource's own elements and its own resource_start and resource_stop, and player events are heard only on dlmp.root.
Matching and order
- A handler matches when its element is the source, or, with
propagate, an ancestor of the source. Handlers of every resource match alike: adlmp.roothandler hears theresource_startof every resource. - Matched handlers run by priority, then in the order they were registered, across all resources. Within one resource the order of registration is the order of the script and module code that registers them.
- The set of handlers is fixed before the first one runs: a handler added or removed in a callback takes effect from the next event.
- Every matched handler runs. A handler cannot stop the others, and the return value matters only for refusable events.
The callback context
While a handler runs, dlmp.event_name is the event's name, dlmp.source the source Element and dlmp.this_resource the resource whose handler it is. They are nil outside callbacks. Command, timer and other callbacks set dlmp.this_resource only.
Arguments
- Each event passes its own arguments; the list is at
dlmp.add_event_handlerindlmp.lua. Lua drops the arguments a function does not declare, so a handler takes only the ones it needs. - Elements arrive as fresh handles for every handler, and tables (such as the
infoofpickup_interact) are fresh for every handler too. - A handle reads the server when it is called, not when the event was raised: a handler sees the state the events and handlers before it left. An optional player, such as a killer who already left, is
nil. resource_startandresource_stoppass the source element only.
Delivery order
Resources get the server's events as one stream, in the order the server committed them, whatever their kind (ADR 0048):
- Events reach handlers one at a time; each handler sees the state the events before it left.
- What a handler causes (a kick, a respawn, a seat change) raises its events after the current one, in the same server step.
- One delivery pass takes at most 8192 events. The rest follow in the next pass, in the same order, and the server logs a warning; a chain of handlers that keep raising each other's events therefore cannot hang the server.
- An event about an element that is gone by the time of delivery follows the rules of Events with a removed source.
Lifecycle events
resource_start and resource_stop are raised on the resource's own dlmp.resource_root (Lifecycle). resource_start is the first place the resource may act on the game; an error in one of its handlers fails the start. An error in a resource_stop handler is logged and the stop goes on.
dlmp.add_event_handler("resource_start", dlmp.resource_root, function()
dlmp.set_spawn_point({ x = 2458.6, y = -1668.4, z = 13.3, heading = 0.0 })
assert(dlmp.create_vehicle({ model = 411, x = 2470.0, y = -1660.0, z = 13.3 }))
end)Refusable events
Some events ask before the server acts: a handler can refuse them by returning false (ADR 0038). They are vehicle_start_enter, vehicle_jack, vehicle_control, trailer_attach, winch_attach, player_remote_enter, player_detonate_satchels, player_stealth_kill, player_chat and player_connecting.
- One handler returning
falserefuses the event;nil,trueand any other value let it through. All handlers run in any case. - They fail closed: a handler that raises an error or runs past the time limit refuses the event, even if it caught the time-limit error with
pcall. player_connectingtakes a reason as the second return value (return false, "Members only"), shown to the player; the first reason given wins.- A refused
player_chatis not broadcast. A resource that wants other text or other recipients refuses it and sends its own line.
local muted = {} -- player id -> true
dlmp.add_event_handler("player_chat", dlmp.root, function(player, text)
if muted[player:id()] then
player:send_message("You are muted.")
return false
end
end)The return value of every other event is ignored.
Commands
Player commands
A line a player types starting with / reaches the server as a command (ClientCommand, ADR 0016).
dlmp.add_command_handler(name, fn, opts) -- true | nil, err
dlmp.remove_command_handler(name, fn) -- true | nil, errnameis the command without the/: not empty, at most 64 bytes, no whitespace; otherwisenil, err. The samenameandfnregistered twice by one resource returnsnil, err.opts.case_sensitiveisfalseby default (ASCII letters match either case);opts.priorityis"high","normal"(the default) or"low".- The handler gets the player followed by the rest of the line split on whitespace, each word a string. It gets no command name and no unsplit text.
- Several resources may handle the same command. Handlers run by priority, then in registration order; the first that returns
trueconsumes the command and the rest do not run. A handler that returns anything else, or raises an error (logged), lets the next one run. - A command no handler consumes gets the answer
Unknown command: /<name>. A bare/is ignored.
dlmp.add_command_handler("heal", function(player, amount)
local ok, err = player:set_health(tonumber(amount) or 100)
if not ok then
player:send_message("heal failed: " .. err)
end
return true
end)Console commands
dlmp.add_console_command(name, fn) -- true | nil, errA console command is a line of the server console, for what only the operator may do.
namefollows the rules of player commands and matches exactly. One resource holds a name: a name that any resource already holds returnsnil, err.- The handler gets the words after the name, each a string, and no player.
printwrites its answer to the server log. An error is logged. - The server's own console commands (
resource,help,status,uptime,version,sver,shutdownandpassword) are matched first; no resource sees apasswordline. A line no resource holds goes on to the server's remaining console commands. - There is no remove function: a console command goes with its resource's stop.
Timers
local timer = dlmp.set_timer(delay_ms, repeats, fn, ...)
timer:cancel()
timer:is_active()delay_msis raised to at least 50.repeatsis the number of runs;0repeats until cancelled.- The extra arguments are passed to
fnon every run. - A timer counts from the server step of the callback that set it; a timer set at load time counts from the step the resource started in. It runs from the server's main loop. A timer that fell behind runs once and is next due one
delay_msafter that run: missed runs are not made up. - An error in
fnis logged; a repeating timer keeps running. - After its last run, after
cancel()and after its resource's stop,is_active()isfalseand the timer never runs again.cancel()on such a timer does nothing. - A timer is not an element: it raises no events, and while it runs
dlmp.event_nameanddlmp.sourcearenil.
local countdown = 10
local timer
timer = dlmp.set_timer(1000, 0, function()
countdown = countdown - 1
dlmp.show_announcement(dlmp.root, { title = tostring(countdown), duration_ms = 900 })
if countdown == 0 then
timer:cancel()
end
end)