C ABI reference (NodeApi)
Generated from sdk/api.toml by sdk/tools/apigen.py docs. Do not edit; edit the schema.
NodeApi (sdk/node.h) is a positional struct of function pointers handed to node_plugin_init. The index shown with each entry is its position in the struct: that order IS the ABI and never changes; new capabilities are appended. Grouping below is by subject, so a group can span several places in the struct. ABI version: 1. Strings are UTF-8 and NUL-terminated; out buffers take a capacity and return the length written or needed; JSON views return the length even when the buffer was too small, so a caller can size and retry.
See Native modules for how a module is built and loaded.
ABI contract
Section titled “ABI contract”Two plain fields at the top of NodeApi. A native module reads them to assert it was built against a compatible server; the loader has already refused a module whose major differs before any of this is touched. Not part of the Lua surface.
uint32_t abi_version; (field, index 0)
Section titled “uint32_t abi_version; (field, index 0)”NODE_ABI_VERSION as the SERVER was built with. The loader has already compared this against the module’s node_plugin_abi() before calling node_plugin_init, so a module that is running at all matches; the field is here so a module can assert it too.
uint32_t struct_size; (field, index 1)
Section titled “uint32_t struct_size; (field, index 1)”sizeof(NodeApi) as the SERVER built it. A module built against an older minor sees a LARGER value than its own sizeof and can rely on every field it knows about; a module built against a newer minor sees a smaller one and must not touch anything past it.
Console and logging
Section titled “Console and logging”Writing to the server console. Every line is tagged; a resource’s plain node.log carries the resource’s own name as the tag, node.logTag uses one of the server’s known tags, node.logCustom a tag of your own in a chosen colour. A log sink lets a resource observe every line the server prints (asynchronously, on the plugin worker).
void log_info(const char* msg) (index 2)
Section titled “void log_info(const char* msg) (index 2)”Lua: node.raw.log
Log under the server’s “Module” / “Warn” / “Error” console tag. Inline, thread-safe.
void log_warn(const char* msg) (index 3)
Section titled “void log_warn(const char* msg) (index 3)”Lua: node.raw.logWarn
Log under the server’s “Module” / “Warn” / “Error” console tag. Inline, thread-safe.
void log_error(const char* msg) (index 4)
Section titled “void log_error(const char* msg) (index 4)”Lua: node.raw.logError
Log under the server’s “Module” / “Warn” / “Error” console tag. Inline, thread-safe.
void log_tag(const char* tag, const char* msg) (index 5)
Section titled “void log_tag(const char* tag, const char* msg) (index 5)”Lua: node.raw.logTag
Log under an explicit server console tag. Known tags (case-insensitive): Core, Net, Res, Mods, Module, Join, Leave, Kick, Veh, Warn, Error, Debug. Unknown tags fall back to “Module”. The line uses the house format (HH:MM:SS Tag > msg) with the tag’s standard color. Inline.
void log_custom(const char* tag, uint32_t rgb, const char* msg) (index 6)
Section titled “void log_custom(const char* tag, uint32_t rgb, const char* msg) (index 6)”Lua: node.raw.logCustom
Print a line with the module’s OWN tag label in a chosen color, in the house line format. rgb is 0xRRGGBB (e.g. 0xFF8800); the server maps it to the nearest ANSI-256 color, so the exact shade depends on the terminal palette. Keep tags <= 6 characters to preserve the column alignment of the console. The line is mirrored ANSI-stripped into logs/server.log like every other line. Inline.
void log_raw(const char* text) (index 7)
Section titled “void log_raw(const char* text) (index 7)”Lua: node.raw.logRaw
Write raw text bypassing the timestamp/tag format (like the startup banner). The text may contain its own ANSI escapes; the copy written to logs/server.log is ANSI-stripped. No newline handling beyond one line per call. Inline.
void set_log_sink(NodeLogSink sink, void* user) (index 8)
Section titled “void set_log_sink(NodeLogSink sink, void* user) (index 8)”Lua: node.raw.setLogSink
Install a log sink that observes - and may suppress - every server log line (see NodeLogSink). ONE sink slot exists per server: the last registration wins, pass (NULL, NULL) to remove. After set_log_sink(NULL, NULL) returns, the previous sink is guaranteed not to be running and will not be called again (safe to free its user data). The framework clears the sink automatically before modules are unloaded. Inline.
void set_console_title(const char* title) (index 9)
Section titled “void set_console_title(const char* title) (index 9)”Lua: node.raw.setConsoleTitle
Set the console window/tab title. On Windows this calls SetConsoleTitle; elsewhere it emits an OSC escape when the terminal supports ANSI (silently does nothing otherwise). Inline.
Server: info, settings, time and metrics
Section titled “Server: info, settings, time and metrics”Read the server’s identity (name, map, version, port, limits), change the runtime limits and the name, read time, and pull the metrics snapshot. Limit changes apply to the next check – a connecting player, a spawn request – and never kick anyone retroactively.
int get_server_name(char* buf, int buflen) (index 53)
Section titled “int get_server_name(char* buf, int buflen) (index 53)”Lua: node.raw.getServerName
Note: [General] Name
The string getters write a NUL-terminated value into buf and return its length (excluding the NUL), or -1 if buf is too small (128 bytes is always enough for name/version; maps can be longer, 256 is safe). All inline.
int get_map(char* buf, int buflen) (index 54)
Section titled “int get_map(char* buf, int buflen) (index 54)”Lua: node.raw.getMap
Note: [General] Map
The string getters write a NUL-terminated value into buf and return its length (excluding the NUL), or -1 if buf is too small (128 bytes is always enough for name/version; maps can be longer, 256 is safe). All inline.
int get_server_version(char* buf, int buflen) (index 55)
Section titled “int get_server_version(char* buf, int buflen) (index 55)”Lua: node.raw.getServerVersion
Note: e.g. “3.9.3”
The string getters write a NUL-terminated value into buf and return its length (excluding the NUL), or -1 if buf is too small (128 bytes is always enough for name/version; maps can be longer, 256 is safe). All inline.
int get_port(void) (index 56)
Section titled “int get_port(void) (index 56)”Lua: node.raw.getPort
Note: TCP+UDP listening port
The string getters write a NUL-terminated value into buf and return its length (excluding the NUL), or -1 if buf is too small (128 bytes is always enough for name/version; maps can be longer, 256 is safe). All inline.
int get_max_players(void) (index 57)
Section titled “int get_max_players(void) (index 57)”Lua: node.raw.getMaxPlayers
Note: [General] MaxPlayers
The string getters write a NUL-terminated value into buf and return its length (excluding the NUL), or -1 if buf is too small (128 bytes is always enough for name/version; maps can be longer, 256 is safe). All inline.
int get_max_cars(void) (index 58)
Section titled “int get_max_cars(void) (index 58)”Lua: node.raw.getMaxCars
Note: [General] MaxCars
The string getters write a NUL-terminated value into buf and return its length (excluding the NUL), or -1 if buf is too small (128 bytes is always enough for name/version; maps can be longer, 256 is safe). All inline.
double get_server_uptime(void) (index 84)
Section titled “double get_server_uptime(void) (index 84)”Lua: node.raw.getServerUptime
Seconds since the server started (monotonic, fractional). Inline.
int64_t get_unix_time(void) (index 85)
Section titled “int64_t get_unix_time(void) (index 85)”Lua: node.raw.getUnixTime
Current unix time in seconds. Inline.
int set_max_cars(int n) (index 86)
Section titled “int set_max_cars(int n) (index 86)”Lua: node.raw.setMaxCars
Changes the live [General] limits (enforced on the next check; no client is kicked retroactively) / the server name label. Values < 0 are rejected (-1). Inline, thread-safe.
int set_max_players(int n) (index 87)
Section titled “int set_max_players(int n) (index 87)”Lua: node.raw.setMaxPlayers
Changes the live [General] limits (enforced on the next check; no client is kicked retroactively) / the server name label. Values < 0 are rejected (-1). Inline, thread-safe.
int set_server_name(const char* name) (index 88)
Section titled “int set_server_name(const char* name) (index 88)”Lua: node.raw.setServerName
Changes the live [General] limits (enforced on the next check; no client is kicked retroactively) / the server name label. Values < 0 are rejected (-1). Inline, thread-safe.
int get_metrics_json(char* buf, int buflen) (index 97)
Section titled “int get_metrics_json(char* buf, int buflen) (index 97)”Lua: node.raw.getMetrics
Writes a JSON object of live server metrics: players, vehicles, uptime, unix time, TCP/UDP packet counters, flood kicks, connection- limiter stats, plugin queue depth, timer/async task counts. Returns the JSON length or -1 when buf is too small (4 KB is plenty).
int is_node_grab_enabled(void) (index 105)
Section titled “int is_node_grab_enabled(void) (index 105)”Lua: node.raw.isNodeGrabEnabled
1 iff the experimental node grabber is enabled in server.toml ([Experimental] NodeGrab). Even when enabled, every request still needs an explicit ALLOW from the fail-closed vehicleNodeGrabRequest hook. Inline.
Players
Section titled “Players”Who is connected, what they are called, where they are and what they sit in. Player ids are small integers assigned at connect and REUSED after a disconnect, so never keep one across a playerLeft. Every getter returns nil (Lua) / a failure code (C) for an id that is not connected.
int get_player_count(void) (index 10)
Section titled “int get_player_count(void) (index 10)”Lua: node.raw.getPlayerCount
Number of connected players. Inline.
int get_players(int* ids, int max) (index 11)
Section titled “int get_players(int* ids, int max) (index 11)”Lua: not exposed
Writes up to max player ids into ids; returns the number written. Inline.
int get_player_name(int id, char* buf, int buflen) (index 12)
Section titled “int get_player_name(int id, char* buf, int buflen) (index 12)”Lua: node.raw.getPlayerName
Writes the NUL-terminated player name into buf; returns the name length (excluding the NUL), or -1 if the player does not exist or buf is too small. Inline.
int is_player_connected(int id) (index 13)
Section titled “int is_player_connected(int id) (index 13)”Lua: node.raw.isPlayerConnected
Returns 1 if a player with this id is currently connected, else 0. Inline.
int get_player_ping(int id) (index 14)
Section titled “int get_player_ping(int id) (index 14)”Lua: node.raw.getPlayerPing
Seconds since the last keepalive ping was received from this player, or -1 if the player does not exist. Node does not measure round-trip latency; this is a connection-freshness metric (0-1 for a healthy client, growing when the client stalls). Inline.
int get_player_current_vehicle(int id) (index 15)
Section titled “int get_player_current_vehicle(int id) (index 15)”Lua: not exposed
globalId of the vehicle this player is currently DRIVING, or -1 if it is not driving one (on foot, a passenger, or not found). For the role-aware lookup that also finds a passenger seat, use get_player_vehicle. Inline.
int get_player_ip(int id, char* buf, int buflen) (index 59)
Section titled “int get_player_ip(int id, char* buf, int buflen) (index 59)”Lua: node.raw.getPlayerIp
Writes the player’s remote IP (IPv4 dotted / IPv6, NUL-terminated; v4-mapped v6 is normalized to plain v4) into buf; returns its length, or -1 if the player does not exist or buf is too small (64 bytes is always enough). Inline.
int get_player_port(int id) (index 60)
Section titled “int get_player_port(int id) (index 60)”Lua: not exposed
The player’s remote TCP port, or -1 if the player does not exist. Inline.
int get_player_position(int id, float out_pos[3]) (index 61)
Section titled “int get_player_position(int id, float out_pos[3]) (index 61)”Lua: not exposed
World position of the player: the vehicle it occupies (any role), or its walking-avatar unicycle when on foot. Writes x,y,z into out_pos and returns 0, or -1 when the player does not exist or no position snapshot arrived yet. Inline.
int get_players_json(char* buf, int buflen) (index 113)
Section titled “int get_players_json(char* buf, int buflen) (index 113)”Lua: node.raw.getPlayers
Connected players as a JSON array of { id, name }. An array of records rather than a map of id to name: a map keyed by a number has no faithful JSON form, so it could not be the same shape in two languages. Returns the length written. Ask with buf = NULL for the size.
int get_player_endpoint_json(int id, char* buf, int buflen) (index 114)
Section titled “int get_player_endpoint_json(int id, char* buf, int buflen) (index 114)”Lua: node.raw.getPlayerEndpoint
A player’s network endpoint as JSON { ip, port }. get_player_ip and get_player_port answer the same two things separately, which is fine for C and awkward for a scripting caller. Returns the length written, -1 if there is no such player. Ask with buf = NULL for the size.
int get_player_position_json(int id, char* buf, int buflen) (index 115)
Section titled “int get_player_position_json(int id, char* buf, int buflen) (index 115)”Lua: node.raw.getPlayerPosition
A player’s last known world position as JSON { x, y, z }, taken from the vehicle they occupy. Returns the length written, -1 when there is no such player or no position has arrived yet. Ask with buf = NULL for the size.
int get_player_vehicle_json(int id, char* buf, int buflen) (index 116)
Section titled “int get_player_vehicle_json(int id, char* buf, int buflen) (index 116)”Lua: node.raw.getPlayerVehicle
What the player is in, as JSON { gid, role } with role “driver” or “passenger”. get_player_current_vehicle answers the driver case only. Returns the length written, -1 if the player is on foot or does not exist. Ask with buf = NULL for the size.
int get_player_inputs_json(int player_id, char* buf, int buflen) (index 132)
Section titled “int get_player_inputs_json(int player_id, char* buf, int buflen) (index 132)”Lua: node.raw.getPlayerInputs
Everything the player is pressing, in one JSON object: { vehicle (the gid driven), controls: { steering, throttle, brake, clutch, parkingBrake, gear? } or null, controlsAgeSeconds, axes: { … } (may be empty), axesAgeSeconds }. Only a DRIVER’s inputs reach the server, so -1 when the player drives nothing (on foot, or a passenger). Returns the length written, -1 when there is no such player or nothing was reported yet; ask with buf = NULL for the size. ABI 1.9.
int get_player_focus_json(int player_id, char* buf, int buflen) (index 138)
Section titled “int get_player_focus_json(int player_id, char* buf, int buflen) (index 138)”Lua: node.raw.getPlayerFocus
The player’s focus point as JSON { pos: { x, y, z }, ageSeconds }: the position the distance-based interest filter uses for this recipient – its head/camera pose when it sends one, else the position of the vehicle it drives. -1 when the player is unknown or no focus was ever recorded. Returns the length written, -1 when there is no such player or nothing was reported yet; ask with buf = NULL for the size. ABI 1.9.
int get_player_head_pose_json(int player_id, char* buf, int buflen) (index 139)
Section titled “int get_player_head_pose_json(int player_id, char* buf, int buflen) (index 139)”Lua: node.raw.getPlayerHeadPose
The player’s last head/camera pose (State::HeadPose, ~10 Hz over UDP) as JSON { pos: { x, y, z }, rot: { x, y, z, w }, freeCamera, ageSeconds }. -1 when the player is unknown or never sent a pose. Returns the length written, -1 when there is no such player or nothing was reported yet; ask with buf = NULL for the size. ABI 1.9.
int get_player_camera_json(int player_id, char* buf, int buflen) (index 140)
Section titled “int get_player_camera_json(int player_id, char* buf, int buflen) (index 140)”Lua: node.raw.getPlayerCamera
The vehicle the player’s camera is on, from its last Vehicle::Camera report, as JSON { vehicle (gid, -1 for none), ageSeconds }. -1 when the player is unknown or never reported. Returns the length written, -1 when there is no such player or nothing was reported yet; ask with buf = NULL for the size. ABI 1.9. In a strict session (player_set_strict, ABI 1.12) a report the server refused to relay – its target is a vehicle the player does not occupy – is recorded too, with “denied”: true in the JSON; the key is absent for a relayed report.
int get_player_session_json(int player_id, char* buf, int buflen) (index 141)
Section titled “int get_player_session_json(int player_id, char* buf, int buflen) (index 141)”Lua: node.raw.getPlayerSession
The player’s session as one JSON object: { id, name, ip, port, connectedSeconds, synced (finished joining), udpConnected (the UDP channel is bound), pingSeconds (since the last keepalive), unicycle (the walking avatar’s gid or -1), vehicle (gid occupied or -1), role (“driver” | “passenger” | “none”), vehicles (count driven), focusAgeSeconds (-1 when no focus), verified (the join ticket was redeemed with the directory, wire v17), guest (no account behind the name: a Test Drive session, or nothing verified), accountId (the directory’s account id, or null), accountRoles (the directory’s role string, “” for a guest), identifiers ([“nodemp:<id>”, “discord:<id>”, “ip:<addr>”, …] as the directory listed them; empty when unverified) }. -1 for an unknown player. Returns the length written, -1 when there is no such player or nothing was reported yet; ask with buf = NULL for the size. ABI 1.9; the account fields since 1.10.
Roles, kicks and bans
Section titled “Roles, kicks and bans”Roles are per-session strings a plugin assigns and reads back (they are cleared at disconnect and mean nothing to the server itself; the client shows a role’s tag next to the name). Kicks disconnect with a reason. Bans are by IP, persist in bans.json across restarts, and refuse FUTURE connects – banning an IP does not drop an existing session; banPlayer does both.
int kick_player(int id, const char* reason) (index 16)
Section titled “int kick_player(int id, const char* reason) (index 16)”Lua: node.raw.kickPlayer
Kicks a player. Returns 0 on success, -1 if the player does not exist. Inline (the disconnect itself completes asynchronously).
int set_player_role(int id, const char* role) (index 62)
Section titled “int set_player_role(int id, const char* role) (index 62)”Lua: node.raw.setPlayerRole
Assigns/reads a free-form role string on a CONNECTED player. Roles are per-session (keyed by player id, cleared on disconnect) - persist anything long-lived yourself via the storage API keyed by IP. set: 0 on success, -1 unknown player (empty role clears). get: role length, -1 unknown player / no role / buf too small.
int get_player_role(int id, char* buf, int buflen) (index 63)
Section titled “int get_player_role(int id, char* buf, int buflen) (index 63)”Lua: node.raw.getPlayerRole
Assigns/reads a free-form role string on a CONNECTED player. Roles are per-session (keyed by player id, cleared on disconnect) - persist anything long-lived yourself via the storage API keyed by IP. set: 0 on success, -1 unknown player (empty role clears). get: role length, -1 unknown player / no role / buf too small.
int ban_player(int id, const char* reason) (index 64)
Section titled “int ban_player(int id, const char* reason) (index 64)”Lua: node.raw.banPlayer
Bans the player’s IP (persisted to bans.json next to server.toml) and kicks it with reason. Returns 0, or -1 if the player is unknown. NOTE: identity is the IP only (VPN-evadable) - a stronger per-install identity needs a wire change and is deferred.
int ban_ip(const char* ip, const char* reason) (index 65)
Section titled “int ban_ip(const char* ip, const char* reason) (index 65)”Lua: node.raw.banIp
Bans/unbans one IP string (normalized like get_player_ip). ban_ip affects FUTURE connects only (existing sessions stay). Returns 0 on success; unban_ip returns -1 when the IP was not banned.
int unban_ip(const char* ip) (index 66)
Section titled “int unban_ip(const char* ip) (index 66)”Lua: node.raw.unbanIp
Bans/unbans one IP string (normalized like get_player_ip). ban_ip affects FUTURE connects only (existing sessions stay). Returns 0 on success; unban_ip returns -1 when the IP was not banned.
int is_ip_banned(const char* ip) (index 67)
Section titled “int is_ip_banned(const char* ip) (index 67)”Lua: node.raw.isBanned
1 iff the IP is currently banned. Inline.
int get_bans_json(char* buf, int buflen) (index 68)
Section titled “int get_bans_json(char* buf, int buflen) (index 68)”Lua: node.raw.getBans
Writes the whole ban list as a NUL-terminated JSON array [{“ip”,“account”,“reason”,“at”,“name”},…] into buf; returns its length or -1 when buf is too small. An IP ban has “ip” set and “account” null; an account ban (v17) has “account” set (the directory’s id) and “ip” empty.
int ban_account(int64_t account_id, const char* reason) (index 142)
Section titled “int ban_account(int64_t account_id, const char* reason) (index 142)”Lua: node.raw.banAccount
Bans a directory account (wire v17) for FUTURE joins: a player whose redeemed ticket names this account is refused at the handshake, whatever address they come from. Persisted in bans.json as “nodemp:<id>” beside the IP bans. Existing sessions stay (ban_player kicks). Returns 0 on success, -1 for a negative id. ABI 1.10.
int unban_account(int64_t account_id) (index 143)
Section titled “int unban_account(int64_t account_id) (index 143)”Lua: node.raw.unbanAccount
Lifts an account ban. Returns 0 on success, -1 when the account was not banned. ABI 1.10.
int is_account_banned(int64_t account_id) (index 144)
Section titled “int is_account_banned(int64_t account_id) (index 144)”Lua: node.raw.isAccountBanned
1 iff the directory account is currently banned on this server. Inline. ABI 1.10.
Strict session: install verification and the camera rule
Section titled “Strict session: install verification and the camera rule”The server-side half of a strict session (ABI 1.12). Every join already runs the game-install check that [General] VerifyGame asks for: the player’s launcher helper compares the install with the game’s own manifest at level size (file lengths), scripts (plus a hash of the game’s lua/ and ui/), full (hash everything, archives included – slow, an audit) or strict (the archive tables of contents against the server’s reference manifest, the whole game root, the user folder), and the server judges the report – a mismatch is a kick unless VerifyGame is off. That report reaches plugins as the playerVerifyReported notification, after the verdict. player_verify asks the helper to run the check again in the middle of the session, at a level of the plugin’s choosing; the answer arrives as another playerVerifyReported. player_set_strict marks a session strict on the server, which changes one relay rule: Vehicle::Camera frames FROM the strict player that target a vehicle it does not drive or occupy are refused – not relayed – so a strict client cannot follow other people’s cars with its camera; a refused report still fires playerCameraChanged and shows up in get_player_camera_json with “denied”: true. Frames from other players to the strict player are unaffected. The check runs on the player’s machine, by a launcher the player could patch: it catches damaged installs and casual edits, and it is not a security boundary.
Contract for the server side (what the generated bindings and the prelude rely on): playerVerifyReported is dispatched like playerSeatChanged – the three-argument notification form with global_id -1 and the report as JSON – for the join-time report and for every mid-session one, after the server’s own verdict; player_verify counts a request as pending for 60 s from the moment it was sent, report or no report, and answers -3 inside that window – the check every join runs does not count, so a player_verify from a playerJoined handler goes out at once; get_player_camera_json writes “denied”: true only for a refused frame of the player’s own and leaves the key out otherwise, so the record’s shape is unchanged for everyone else.
int player_verify(int player_id, int level) (index 151)
Section titled “int player_verify(int player_id, int level) (index 151)”Lua: node.raw.verifyPlayer
Asks the player’s launcher helper to verify the game install again, now, at level: NODE_VERIFY_SIZE (1, every manifest entry’s length), NODE_VERIFY_SCRIPTS (2, plus a hash of the game’s lua/ and ui/), NODE_VERIFY_FULL (3, hash everything, archives included – slow, an audit) or NODE_VERIFY_STRICT (4, the archive tables of contents against the server’s reference manifest, the whole game root, the user folder). The request goes out as a VerifyRequest in the middle of the session; the helper answers when it has finished (seconds for size, longer for full), and the answer arrives as the “playerVerifyReported” notification – with the server’s own verdict already applied: a “differs” or “failed” report kicks the player when [General] VerifyGame is not off, exactly as at join time, before the event fires. Returns 0 when the request was sent, -1 when no player has that id, -2 when that level is not available for this player – the client’s launcher is too old for it, or the level was not on offer (strict needs a reference manifest on the server; a level outside 1..4 is never available), -3 when a verification is already pending for this player: at most one request per player per 60 s, and a request counts as pending for those 60 s whether or not its report has arrived. The check every join runs does not count: a player_verify from a playerJoined handler goes out at once. Inline (the request is written directly). ABI 1.12.
int player_set_strict(int player_id, int on) (index 152)
Section titled “int player_set_strict(int player_id, int on) (index 152)”Lua: node.raw.setPlayerStrict
Marks the player’s session strict (on nonzero) or lifts the mark (0). A strict session changes one relay rule, outbound only: a Vehicle::Camera frame FROM the player that targets a vehicle it does not drive or ride in is refused – not relayed, so nobody sees it spectating. Frames from other players to it are unaffected. A refused frame still updates get_player_camera_json and fires “playerCameraChanged”, and the record then carries “denied”: true (the key is absent otherwise), so an observer can tell a spectate attempt from a switch to an own car. Server-side only: it tells the client nothing by itself – the resource sends its own session config for that. Per session, cleared at disconnect. Returns 0 on success, -1 when no player has that id. Inline. ABI 1.12.
Vehicles: registry and records
Section titled “Vehicles: registry and records”The vehicle registry: global ids, who spawned and who drives each one, the occupants, and the full JSON record. Global ids are unique for the life of the server process. A vehicle’s spawner may be a player or -1 for a server-spawned one; its sync AUTHORITY is the client that simulates it and is not necessarily the spawner.
int get_vehicle_count(void) (index 17)
Section titled “int get_vehicle_count(void) (index 17)”Lua: node.raw.getVehicleCount
Number of vehicles currently registered in the world. Inline.
int get_vehicles(int* ids, int max) (index 18)
Section titled “int get_vehicles(int* ids, int max) (index 18)”Lua: not exposed
Writes up to max vehicle globalIds into ids; returns the number written. Inline.
int get_vehicle_info(int global_id, NodeVehicleInfo* out) (index 19)
Section titled “int get_vehicle_info(int global_id, NodeVehicleInfo* out) (index 19)”Lua: not exposed
Fills out with a snapshot of the vehicle. Returns 0 on success, -1 if the vehicle does not exist (out is left untouched). Inline.
int get_player_vehicles(int player_id, int* ids, int max) (index 20)
Section titled “int get_player_vehicles(int player_id, int* ids, int max) (index 20)”Lua: node.raw.getPlayerVehicles
Writes up to max globalIds of vehicles this player spawned or is driving into ids; returns the number written. Inline.
int get_player_vehicle(int player_id, int* out_role) (index 21)
Section titled “int get_player_vehicle(int player_id, int* out_role) (index 21)”Lua: not exposed
The single vehicle a player currently occupies in ANY role (wire v9). Returns its globalId, or -1 when the player is on foot / not found. When out_role is non-NULL it receives the seat role (NODE_ROLE_NONE/DRIVER/ PASSENGER). Inline.
int get_vehicle_occupants(int global_id, int* ids, int* roles, int max) (index 22)
Section titled “int get_vehicle_occupants(int global_id, int* ids, int* roles, int max) (index 22)”Lua: not exposed
Occupants of a vehicle (wire v9): the driver first (role NODE_ROLE_DRIVER) then each passenger (NODE_ROLE_PASSENGER). Writes up to max player ids into ids and, when roles is non-NULL, the matching role into roles[i]; returns the occupant count (0 if the vehicle does not exist or is empty). Inline.
int get_vehicle_json(int global_id, char* buf, int buflen) (index 111)
Section titled “int get_vehicle_json(int global_id, char* buf, int buflen) (index 111)”Lua: node.raw.getVehicle
The complete vehicle record as a JSON object: id, spawner, driver, authority, isUnicycle, name, idleSeconds, passengers, spawnerOnline, lockMode, lockWhitelist, configHash, damageCounter, damageBlobCounter, damageBlobSize, tags. get_vehicle_info fills a fixed struct and cannot carry the variable-length parts (passengers, the lock whitelist, tags), which is why this exists alongside it. Returns the length written, -1 if there is no such vehicle. Ask with buf = NULL for the size.
int get_vehicles_json(char* buf, int buflen) (index 112)
Section titled “int get_vehicles_json(char* buf, int buflen) (index 112)”Lua: node.raw.getVehicles
Every vehicle as a JSON array of the records get_vehicle_json returns. Returns the length written. Ask with buf = NULL for the size.
int get_vehicle_occupants_json(int global_id, char* buf, int buflen) (index 118)
Section titled “int get_vehicle_occupants_json(int global_id, char* buf, int buflen) (index 118)”Lua: node.raw.getVehicleOccupants
Who is in a vehicle, driver first, as a JSON array of { playerId, role } with role “driver” or “passenger”. get_vehicle_occupants returns the same information as two parallel arrays, which is harder to consume from a scripting language. Returns the length written, -1 if there is no such vehicle. Ask with buf = NULL for the size.
Vehicles: state, live and cached
Section titled “Vehicles: state, live and cached”What the server knows about a vehicle’s state. LIVE state (transform, controls) comes from the last position/state snapshot the authority sent and is a few ticks old. CACHED state (config, couplers, powertrain, engine, controllers, electrics, damage) is the merge of everything relayed so far and is what a late joiner receives. Config hash is the FNV-1a-32 marker clients echo so the server can spot a desynced config.
int get_vehicle_damage_counter(int global_id) (index 23)
Section titled “int get_vehicle_damage_counter(int global_id) (index 23)”Lua: not exposed
The damage version last reported by the vehicle’s sync authority (0 = pristine; grows as the vehicle takes damage; resets on repair/ respawn). -1 if the vehicle does not exist. Pair with the “vehicleDamageChanged” builtin to detect changes and offer a resync (resync_vehicle). Inline.
int get_vehicle_transform(int global_id, NodeVehicleTransform* out) (index 24)
Section titled “int get_vehicle_transform(int global_id, NodeVehicleTransform* out) (index 24)”Lua: not exposed
Fills out with the vehicle’s kinematics decoded from its last State/Pos snapshot (the high-rate stream the sync authority sends over UDP). Returns 0 on success, -1 if the vehicle does not exist or no snapshot has arrived yet (out is left untouched). Inline.
int get_vehicle_controls(int global_id, NodeVehicleControls* out) (index 25)
Section titled “int get_vehicle_controls(int global_id, NodeVehicleControls* out) (index 25)”Lua: not exposed
Fills out with the driving controls folded into the same snapshot. Returns 0 on success, -1 if the vehicle does not exist, no snapshot has arrived yet, or the last snapshot carried no valid controls (the sender was not the driver); out is left untouched on -1. Inline.
int get_vehicle_electrics_json(int global_id, char* buf, int buflen) (index 26)
Section titled “int get_vehicle_electrics_json(int global_id, char* buf, int buflen) (index 26)”Lua: node.raw.getVehicleElectrics
Writes the vehicle’s current electrics (the merged view of the State/Electrics delta stream: lights, signals, gauges, …) as a NUL-terminated JSON object string into buf. Returns the string length (excluding the NUL), or -1 if the vehicle does not exist, no electrics arrived yet, or buf is too small (16 KB is comfortably enough in practice). Inline.
int get_vehicle_config_json(int global_id, char* buf, int buflen) (index 69)
Section titled “int get_vehicle_config_json(int global_id, char* buf, int buflen) (index 69)”Lua: node.raw.getVehicleConfig
The vehicle’s current spawn/edit config JSON (what a joiner receives inside Vehicle::Spawn).
int get_vehicle_couplers_json(int global_id, char* buf, int buflen) (index 70)
Section titled “int get_vehicle_couplers_json(int global_id, char* buf, int buflen) (index 70)”Lua: node.raw.getVehicleCouplers
Merged coupler/door state as a JSON array of coupler entries (the same schema the clients exchange; one entry per known coupler).
int get_vehicle_powertrain_json(int global_id, char* buf, int buflen) (index 71)
Section titled “int get_vehicle_powertrain_json(int global_id, char* buf, int buflen) (index 71)”Lua: node.raw.getVehiclePowertrain
Merged powertrain / engine device state objects.
int get_vehicle_engine_json(int global_id, char* buf, int buflen) (index 72)
Section titled “int get_vehicle_engine_json(int global_id, char* buf, int buflen) (index 72)”Lua: node.raw.getVehicleEngine
Merged powertrain / engine device state objects.
int get_vehicle_controllers_json(int global_id, char* buf, int buflen) (index 73)
Section titled “int get_vehicle_controllers_json(int global_id, char* buf, int buflen) (index 73)”Lua: node.raw.getVehicleControllers
Merged controller-call map: {“controller|function”: {call json}}.
int get_vehicle_damage_blob(int global_id, uint8_t* buf, int buflen, uint32_t* out_counter) (index 74)
Section titled “int get_vehicle_damage_blob(int global_id, uint8_t* buf, int buflen, uint32_t* out_counter) (index 74)”Lua: not exposed
Copies the vehicle’s baked-damage blob into buf and returns its size (0 = pristine). When out_counter is non-NULL it receives the damage counter the blob was made at. buf may be NULL to query the size (buflen ignored then); with a non-NULL buf that is too small, -1. Returns -1 when the vehicle does not exist.
int get_world_snapshot_json(char* buf, int buflen) (index 75)
Section titled “int get_world_snapshot_json(char* buf, int buflen) (index 75)”Lua: node.raw.getWorldSnapshot
One-call world snapshot: {“time”:unix,“players”:[{id,name,ip,vehicle, role,pos:[x,y,z]|null}],“vehicles”:[{id,spawner,driver,authority, unicycle,pos,rot,vel}]} - the bulk position read for radars/maps. Returns the JSON length or -1 when buf is too small.
int get_vehicle_config_hash(int global_id, uint32_t* out_hash) (index 104)
Section titled “int get_vehicle_config_hash(int global_id, uint32_t* out_hash) (index 104)”Lua: node.raw.getVehicleConfigHash
The vehicle’s current config marker (FNV-1a-32 over the cached config JSON bytes exactly as Spawn/Resync send them) – what clients echo in their periodic Vehicle::ConfigHash reports. Writes it into out_hash and returns 0, or -1 for an unknown vehicle. Inline.
int get_vehicle_damage_json(int global_id, char* buf, int buflen) (index 117)
Section titled “int get_vehicle_damage_json(int global_id, char* buf, int buflen) (index 117)”Lua: node.raw.getVehicleDamage
Damage telemetry as JSON { counter, blobCounter, size }: the authority-reported damage version, the version the stored blob was made at, and the blob size (0 = pristine). Returns the length written, -1 if there is no such vehicle. Ask with buf = NULL for the size.
int get_vehicle_transform_json(int global_id, char* buf, int buflen) (index 120)
Section titled “int get_vehicle_transform_json(int global_id, char* buf, int buflen) (index 120)”Lua: node.raw.getVehicleTransform
Kinematics from the last position snapshot as JSON: pos, rot (a quaternion), vel, angVel, time, ping, seq, paused, transitioning, teleport, velocityStep, hasControls, ageSeconds. Velocities are m/s, angular velocity rad/s, time and ping the sender’s own timers in seconds, ageSeconds since the server accepted the snapshot. teleport / velocityStep are the sender’s step flags for that snapshot (receivers snap instead of blending); hasControls says whether the control bytes were meaningful (the sender was the driver). get_vehicle_transform fills a fixed struct with the same values. Returns the length written, -1 if there is no such vehicle or no snapshot arrived. Ask with buf = NULL for the size.
int get_vehicle_controls_json(int global_id, char* buf, int buflen) (index 121)
Section titled “int get_vehicle_controls_json(int global_id, char* buf, int buflen) (index 121)”Lua: node.raw.getVehicleControls
Driving controls from the same snapshot as JSON: steering, throttle, brake, clutch, parkingBrake, and gear when the sender reported one – a number for manual boxes, a letter for the automatic ones (“P”, “R”, “N”, “D”, “S”, “2”, “1”, “M3”). Returns the length written, -1 until a snapshot arrives whose controls are meaningful, which is only when the sender was the driver. Ask with buf = NULL for the size.
int get_vehicle_inputs_json(int global_id, char* buf, int buflen) (index 131)
Section titled “int get_vehicle_inputs_json(int global_id, char* buf, int buflen) (index 131)”Lua: node.raw.getVehicleInputs
The vehicle’s non-core input axes as JSON { axes: { name: value, … }, ageSeconds }. State::Inputs carries only the axes that are NOT in the Pos snapshot (modded/extra inputs; the five driving controls and gear are get_vehicle_controls_json) and is delta-gated – a value is sent only when it changes – so the registry merges the deltas and this is the CURRENT set. ageSeconds is since the last delta. Returns the length written, -1 when there is no such vehicle or nothing arrived yet; ask with buf = NULL for the size. ABI 1.9.
int get_vehicle_nodes_json(int global_id, char* buf, int buflen) (index 133)
Section titled “int get_vehicle_nodes_json(int global_id, char* buf, int buflen) (index 133)”Lua: node.raw.getVehicleNodes
The last State::Nodes body the vehicle’s authority sent, as the JSON text it sent (validated, not re-encoded): node positions / deformation in the client’s nodemp.sync.nodes schema. Cached for the join replay, so this costs nothing extra. Returns the length written, -1 when there is no such vehicle or nothing arrived yet; ask with buf = NULL for the size. ABI 1.9.
int get_vehicle_break_groups_json(int global_id, char* buf, int buflen) (index 134)
Section titled “int get_vehicle_break_groups_json(int global_id, char* buf, int buflen) (index 134)”Lua: node.raw.getVehicleBreakGroups
The set of break groups reported broken on the vehicle, merged from the delta stream, as a JSON array of group names. Returns the length written, -1 when there is no such vehicle or nothing arrived yet; ask with buf = NULL for the size. ABI 1.9.
int get_vehicle_paints_json(int global_id, char* buf, int buflen) (index 135)
Section titled “int get_vehicle_paints_json(int global_id, char* buf, int buflen) (index 135)”Lua: node.raw.getVehiclePaints
The vehicle’s paints as a JSON array (the client vcf.paints schema: one entry per paint layer, baseColor and the material fields), taken from the cached spawn/edit config – Paint packets are merged into it. Empty array when the config carries none. -1 only for an unknown vehicle. Returns the length written, -1 when there is no such vehicle or nothing arrived yet; ask with buf = NULL for the size. ABI 1.9.
int get_vehicle_sync_json(int global_id, char* buf, int buflen) (index 136)
Section titled “int get_vehicle_sync_json(int global_id, char* buf, int buflen) (index 136)”Lua: node.raw.getVehicleSync
Sync health of the vehicle as JSON: { authority (player id simulating it, -1 none), authorityAgeSeconds (since its last accepted Pos or its assignment – the liveness heartbeat behind grace/release), seatEpoch (the seat/authority transaction counter), spawnedAgeSeconds, idleSeconds (0 while driven), configChangedAgeSeconds, hasPos, posAgeSeconds (since the last accepted Pos), posSeq }. Returns the length written, -1 when there is no such vehicle or nothing arrived yet; ask with buf = NULL for the size. ABI 1.9.
int get_vehicle_transforms_json(char* buf, int buflen) (index 137)
Section titled “int get_vehicle_transforms_json(char* buf, int buflen) (index 137)”Lua: node.raw.getVehicleTransforms
Every vehicle’s last kinematic snapshot in one JSON array – the shape of get_vehicle_transform_json plus id – taken under one registry lock without copying vehicle state. Vehicles with no snapshot yet are absent. Ascending by id. Returns the length written; ask with buf = NULL for the size. ABI 1.9.
Vehicles: control
Section titled “Vehicles: control”Acting on vehicles from the server: delete, spawn ownerless vehicles, open and close couplers, seat, unseat and swap players, lock vehicles against new seat claims, execute a controller call on the sync authority, and resync a vehicle or a player. Server-driven seating bypasses locks; locks gate NEW client claims only and never evict anyone.
int delete_vehicle(int global_id) (index 27)
Section titled “int delete_vehicle(int global_id) (index 27)”Lua: node.raw.deleteVehicle
Deletes a vehicle: broadcasts the removal to every client, removes it from the registry and fires the “vehicleDeleted” builtin (delivered asynchronously on the worker thread). Returns 0 on success, -1 if the vehicle does not exist. Inline.
int delete_all_vehicles(void) (index 28)
Section titled “int delete_all_vehicles(void) (index 28)”Lua: node.raw.deleteAllVehicles
Deletes every vehicle in the world; returns how many were deleted. Inline.
int set_vehicle_coupler(int global_id, const char* name, int open) (index 29)
Section titled “int set_vehicle_coupler(int global_id, const char* name, int open) (index 29)”Lua: node.raw.setVehicleCoupler
Server-driven coupler/door actuation: opens (open = 1) or closes (open = 0) the named advanced-coupler group (e.g. “doorFLCoupler”, “hood_latch_coupler”) on EVERY client - including the driver - and merges the resulting absolute state into the join-replay cache, so a late joiner sees the door in its new state. Skips the vehicleCouplerRequest permission builtin (the server never asks itself). Returns 0 on success, -1 if the vehicle does not exist. Inline.
int seat_player(int player_id, int global_id, int seat_kind) (index 30)
Section titled “int seat_player(int player_id, int global_id, int seat_kind) (index 30)”Lua: not exposed
Server-driven seating (Vehicle Authority V2). These move a player between driver seats WITHOUT the cancellable vehicleEnterRequest/vehicleExitRequest hooks (the server never asks itself, like set_vehicle_coupler) but emit the same Driver/Authority/SeatFree/PlayerVehicle broadcasts, so the target client’s camera/control follow exactly like an ordinary enter/exit and observers fire as usual. All inline; the seat mutation itself completes before the call returns, the client-side camera move follows asynchronously over the network. seat_player: seats player_id in global_id. seat_kind is NODE_SEAT_DRIVER or NODE_SEAT_PASSENGER. As a DRIVER the player leaves any car it drives, its walking-avatar unicycle is deleted, and a previous driver is put on foot. As a PASSENGER it joins the occupant list (allowed even in a driverless car; the wheel is untouched). Returns 0 on success, -1 on invalid args or a unicycle target.
int unseat_player(int player_id) (index 31)
Section titled “int unseat_player(int player_id) (index 31)”Lua: node.raw.unseatPlayer
unseat_player: forces the player out of the car it occupies as DRIVER or PASSENGER (on foot; the client spawns its unicycle as on a normal exit). Returns 0 on success, -1 if the player is on foot (in no vehicle).
int swap_players(int player_a, int player_b) (index 32)
Section titled “int swap_players(int player_a, int player_b) (index 32)”Lua: node.raw.swapPlayers
swap_players: atomically exchanges the two players’ seats – role AND vehicle – in one seat transaction, implementing the full driver/ passenger/pedestrian matrix (driver<->driver swaps cars; driver<-> passenger of one car exchanges roles; a seat<->pedestrian swap rides one and walks the other, …). Returns 0 on success, -1 when player_a == player_b, both are on foot, or they share the same passenger seat.
int resync_vehicle(int global_id, int player_id) (index 33)
Section titled “int resync_vehicle(int global_id, int player_id) (index 33)”Lua: node.raw.resyncVehicle
Re-sends a vehicle’s full cached authoritative state (spawn/config, position, electrics, doors/couplers, powertrain, controller state, driver/authority; the damage blob rides along) to one player, or to every synced player when player_id is -1. Pass global_id -1 to resync ALL vehicles. Returns the number of vehicle bundles sent. The client re-applies the bundle like a join replay (“any desync can always be made a sync”). Inline.
int resync_player(int player_id) (index 34)
Section titled “int resync_player(int player_id) (index 34)”Lua: node.raw.resyncPlayer
Re-sends EVERY vehicle’s full cached state to one player; equivalent to resync_vehicle(-1, player_id). Returns the number of vehicle bundles sent. Inline.
int spawn_vehicle(const char* config_json, const char* owner_name) (index 76)
Section titled “int spawn_vehicle(const char* config_json, const char* owner_name) (index 76)”Lua: node.raw.spawnVehicle
Spawns a vehicle FROM THE SERVER: registers it (spawner -1 = ownerless), broadcasts the Vehicle::Spawn to every client and assigns the best available sync authority (nobody takes the driver seat; use seat_player to seat someone). config_json must be a JSON object in the same schema clients send in SpawnReq (jbm, vcf, pos, rot, …); unicycle configs are rejected. owner_name is the display label (NULL -> “server”). Returns the new globalId, or -1 on invalid config. Inline; fires the “vehicleSpawned” builtin asynchronously.
int set_vehicle_lock(int global_id, int mode, const int* whitelist, int count) (index 101)
Section titled “int set_vehicle_lock(int global_id, int mode, const int* whitelist, int count) (index 101)”Lua: not exposed
Sets the vehicle’s lock: mode is NODE_LOCK_ above, whitelist is an array of count player ids that are ALWAYS allowed (NULL/0 = empty; <= 32 entries, an over-limit set is rejected). “unlocked” clears the whitelist. Broadcast to every client as Vehicle::Lock, replayed to late joiners, fires the “vehicleLockChanged” builtin. Returns 0 on success, -1 on an unknown vehicle / bad mode / over-limit whitelist. Inline.
int get_vehicle_lock(int global_id, int* out_whitelist, int max, int* out_count) (index 102)
Section titled “int get_vehicle_lock(int global_id, int* out_whitelist, int max, int* out_count) (index 102)”Lua: not exposed
Reads the vehicle’s lock: returns the NODE_LOCK_ mode (or -1 for an unknown vehicle) and, when out_whitelist is non-NULL, writes up to max whitelisted player ids into it, storing the count in out_count (out_count may be NULL). Inline.
int trigger_vehicle(int global_id, const char* call_json) (index 103)
Section titled “int trigger_vehicle(int global_id, const char* call_json) (index 103)”Lua: node.raw.triggerVehicle
Has the vehicle’s SYNC AUTHORITY execute one controller call (call_json = the Vehicle::Trigger schema: {“controllerName”:…, “functionName”:…, …}). Skips the vehicleTriggerRequest hook (the server never asks itself); never cached – the resulting state changes ride the normal sync channels. Returns 0 on success, -1 when the vehicle is gone, idle (no authority) or call_json is not a JSON object. Inline.
int get_vehicle_lock_json(int global_id, char* buf, int buflen) (index 122)
Section titled “int get_vehicle_lock_json(int global_id, char* buf, int buflen) (index 122)”Lua: node.raw.getVehicleLock
The vehicle lock as JSON { mode, whitelist }: mode is “unlocked”, “locked” or “driver-only”, whitelist the player ids allowed in regardless. get_vehicle_lock answers the same through an out array and an out count. Returns the length written, -1 if there is no such vehicle. Ask with buf = NULL for the size.
int set_vehicle_lock_named(int global_id, const char* mode, const char* whitelist_json) (index 123)
Section titled “int set_vehicle_lock_named(int global_id, const char* mode, const char* whitelist_json) (index 123)”Lua: node.raw.setVehicleLock
Sets the lock by NAMED mode: “unlocked”, “locked” or “driver-only” (case-insensitive; “lockedAll” and “driverOnly” are accepted too). whitelist_json is an optional JSON array of the player ids allowed in regardless – NULL for none. set_vehicle_lock takes the wire’s integer and a C array instead, which is the transport’s business rather than the caller’s. Returns 0, or -1 for an unknown mode, an unparseable whitelist or an unknown vehicle.
int seat_player_role(int player_id, int global_id, const char* role) (index 124)
Section titled “int seat_player_role(int player_id, int global_id, const char* role) (index 124)”Lua: node.raw.seatPlayer
Seats a player by NAMED role: “driver” (the default when role is NULL or empty) or “passenger”. seat_player takes the wire’s integer instead, which is the transport’s business rather than the caller’s. Returns 0 on success, -1 for an unknown role or when the seat cannot be taken.
Vehicle tags
Section titled “Vehicle tags”Key/value strings a plugin attaches to any vehicle. Tags survive edits, resets and respawns, ride the join replay to late joiners and are broadcast to every client on change (read-only client-side, readable via the vehicle record). Limits: key 1-64 bytes, value 1-256 bytes, 32 distinct keys per vehicle – a set that breaks a limit is rejected, never truncated.
int set_vehicle_tag(int global_id, const char* key, const char* value) (index 35)
Section titled “int set_vehicle_tag(int global_id, const char* key, const char* value) (index 35)”Lua: node.raw.setVehicleTag
Stores/overwrites one tag. Returns 0 on success, -1 when the vehicle does not exist, key/value is NULL or empty, or a limit is broken (empty values are not legal tags – remove with remove_vehicle_tag).
int get_vehicle_tag(int global_id, const char* key, char* buf, int buflen) (index 36)
Section titled “int get_vehicle_tag(int global_id, const char* key, char* buf, int buflen) (index 36)”Lua: node.raw.getVehicleTag
Writes the NUL-terminated tag value into buf; returns the value length (excluding the NUL), or -1 if the vehicle or the tag does not exist or buf is too small (257 bytes always fits).
int remove_vehicle_tag(int global_id, const char* key) (index 37)
Section titled “int remove_vehicle_tag(int global_id, const char* key) (index 37)”Lua: node.raw.removeVehicleTag
Removes one tag. Returns 0 when the tag existed and was removed, -1 otherwise.
int get_vehicle_tags_json(int global_id, char* buf, int buflen) (index 38)
Section titled “int get_vehicle_tags_json(int global_id, char* buf, int buflen) (index 38)”Lua: node.raw.getVehicleTags
Writes the vehicle’s whole tag set as a NUL-terminated JSON object string ({“key”:“value”,…}, keys sorted) into buf. Returns the string length (excluding the NUL), or -1 if the vehicle does not exist or buf is too small (16 KB always fits the 32x(64+256) worst case).
int get_vehicles_by_tag(const char* key, const char* value_or_null, int* out_ids, int max) (index 39)
Section titled “int get_vehicles_by_tag(const char* key, const char* value_or_null, int* out_ids, int max) (index 39)”Lua: node.raw.getVehiclesByTag
Reverse lookup: writes up to max globalIds of vehicles carrying the tag key into out_ids – any value when value_or_null is NULL, exactly value_or_null otherwise. Returns the number written, sorted ascending by globalId. For a multi-pair AND match call this once and intersect, or use the Lua node.getVehiclesByTags.
int get_vehicles_by_tags(const char* pairs_json, int* out_ids, int max) (index 77)
Section titled “int get_vehicles_by_tags(const char* pairs_json, int* out_ids, int max) (index 77)”Lua: node.raw.getVehiclesByTags
Writes up to max globalIds of vehicles whose tag set contains EVERY key/value pair of pairs_json (a JSON object {“key”:“value”,…}); returns the number written, ascending. An empty/invalid object matches nothing. The C mirror of Lua node.getVehiclesByTags.
Visibility: relay filter and groups
Section titled “Visibility: relay filter and groups”Deciding who sees what. The relayRequest filter (formerly canRelay) answers, per (sender, receiver, packet category, subtype, vehicle), whether a packet is forwarded; verdicts are cached until invalidated. Visibility groups are the coarse form: a player only receives relays from players and vehicles in the same group (group 0 is everyone). The dimensions module is built on groups.
void register_relay_filter(NodeCanRelayCallback cb, void* user) (index 78)
Section titled “void register_relay_filter(NodeCanRelayCallback cb, void* user) (index 78)”Lua: node.raw.on
void unregister_relay_filter(NodeCanRelayCallback cb, void* user) (index 79)
Section titled “void unregister_relay_filter(NodeCanRelayCallback cb, void* user) (index 79)”Lua: node.raw.off
void invalidate_relay_cache(void) (index 80)
Section titled “void invalidate_relay_cache(void) (index 80)”Lua: node.raw.invalidateRelayCache
Drops every cached relay verdict so filters are re-consulted. Call after the data your filter reads changed. Inline, thread-safe.
void set_player_group(int player_id, int64_t group) (index 125)
Section titled “void set_player_group(int player_id, int64_t group) (index 125)”Lua: node.raw.setPlayerGroup
Puts a player in a visibility group. Two things are visible to each other only when their group numbers match, and 0 – the default – is the world everyone shares. That is the whole rule, and it covers parallel worlds, instanced races, private lobbies and spectator islands.
Prefer this over a relay filter for anything of the kind. A filter lives in a script, scripts run on the framework worker, and the question is asked on the network threads – so every answer is either a wait or a guess. Comparing two numbers needs neither, and cannot be late.
Groups are forgotten when the player disconnects: ids are reused, and a stale group would put the next holder of that id in someone else’s world.
void set_vehicle_group(int global_id, int64_t group) (index 126)
Section titled “void set_vehicle_group(int global_id, int64_t group) (index 126)”Lua: node.raw.setVehicleGroup
Puts a vehicle in a visibility group; 0 is the shared world. A packet about a vehicle belongs to the VEHICLE’s group, not its driver’s, because a car can be moved between worlds with someone sitting in it. Forgotten when the vehicle is deleted.
int64_t get_player_group(int player_id) (index 127)
Section titled “int64_t get_player_group(int player_id) (index 127)”Lua: node.raw.getPlayerGroup
The player’s visibility group, 0 when it has never been set or the player is unknown.
int64_t get_vehicle_group(int global_id) (index 128)
Section titled “int64_t get_vehicle_group(int global_id) (index 128)”Lua: node.raw.getVehicleGroup
The vehicle’s visibility group, 0 when it has never been set or there is no such vehicle.
Events: subscribe and emit
Section titled “Events: subscribe and emit”The event system has three kinds of name. ENGINE events are camelCase builtins the server fires, named <subject><Verb-ed> (playerJoined, vehicleSpawned); the requests a handler can deny are named <subject><Action>Request (playerConnectRequest, vehicleSpawnRequest; relayRequest is the relay filter), and a handler that returns false denies the action. WIRE events are <domain>:<verb> strings a resource defines itself, sent with emitClient/emitAll and received from clients through the same node.on. One node.on covers all of them: the name decides the kind. The pre-1.2.0 names (playerJoin, onPlayerConnectRequest, canRelay, …) are deprecated aliases: they still subscribe to the same event and log one warning per resource per old name; they are removed in 2.0.
int emit_client(int id, const char* event, const char* data) (index 40)
Section titled “int emit_client(int id, const char* event, const char* data) (index 40)”Lua: node.raw.emitClient
Sends one custom EVENT frame (name + data) to one client over TCP. Returns 0 on success, -1 on failure (unknown player, or player not joined yet). Inline.
void emit_all(const char* event, const char* data) (index 41)
Section titled “void emit_all(const char* event, const char* data) (index 41)”Lua: node.raw.emitAll
Sends one custom EVENT frame to all fully-connected clients. Inline.
void register_client_event(const char* event, NodeClientEventCallback cb, void* user) (index 42)
Section titled “void register_client_event(const char* event, NodeClientEventCallback cb, void* user) (index 42)”Lua: node.raw.on
Subscribes to a custom client event by name. The callback runs on the framework worker thread. Inline.
void unregister_client_event(const char* event, NodeClientEventCallback cb, void* user) (index 43)
Section titled “void unregister_client_event(const char* event, NodeClientEventCallback cb, void* user) (index 43)”Lua: node.raw.off
Removes a client-event subscription previously made with register_client_event. Both cb AND user must match the registration. A dispatch already in flight on the worker thread may still deliver one final call. Inline.
void register_builtin_event(const char* name, NodeBuiltinEventCallback cb, void* user) (index 44)
Section titled “void register_builtin_event(const char* name, NodeBuiltinEventCallback cb, void* user) (index 44)”Lua: node.raw.on
Subscribes an OBSERVER to a builtin event by name (see NodeBuiltinEventCallback). This also observes the cancellable request builtins without being able to deny them. Unknown builtin names are rejected with a console error. Inline.
void unregister_builtin_event(const char* name, NodeBuiltinEventCallback cb, void* user) (index 45)
Section titled “void unregister_builtin_event(const char* name, NodeBuiltinEventCallback cb, void* user) (index 45)”Lua: node.raw.off
Removes a builtin-event subscription previously made with register_builtin_event. Both cb AND user must match. Same in-flight caveat as unregister_client_event. Inline.
void register_cancellable_event(const char* name, NodeCancellableEventCallback cb, void* user) (index 46)
Section titled “void register_cancellable_event(const char* name, NodeCancellableEventCallback cb, void* user) (index 46)”Lua: node.raw.on
Subscribes a VERDICT callback to one of the cancellable request builtins (see NodeCancellableEventCallback for the names and payloads). The callback runs on the framework worker thread and returns nonzero to deny the request. Unknown or non-cancellable names are rejected with a console error. Inline.
void unregister_cancellable_event(const char* name, NodeCancellableEventCallback cb, void* user) (index 47)
Section titled “void unregister_cancellable_event(const char* name, NodeCancellableEventCallback cb, void* user) (index 47)”Lua: node.raw.off
Removes a verdict subscription previously made with register_cancellable_event. Both cb AND user must match. A dispatch already in flight on the worker thread may still deliver one final call. Inline.
void register_vehicle_event(const char* name, NodeVehicleEventCallback cb, void* user) (index 48)
Section titled “void register_vehicle_event(const char* name, NodeVehicleEventCallback cb, void* user) (index 48)”Lua: node.raw.on
Subscribes an OBSERVER to a notification event by name (see NodeVehicleEventCallback: “vehicleEdited”, “vehicleReset”, “vehiclePainted”, “playerSeatChanged”, “vehicleCouplerChanged”, “vehicleControllerChanged”, “playerVerifyReported” – the server judged a report the player’s launcher sent; global_id is -1 there – and “resourceUnload” – (-1, -1, reason) right before the registering resource’s state is dropped, reason “reload” or “shutdown”; dispatched to that resource’s own handlers only, so only a hosted resource ever receives it, never a native module). The callback runs on the framework worker thread and carries (player_id, global_id, data); its return value is ignored - the action already happened. Unknown names are rejected with a console error. Inline.
void unregister_vehicle_event(const char* name, NodeVehicleEventCallback cb, void* user) (index 49)
Section titled “void unregister_vehicle_event(const char* name, NodeVehicleEventCallback cb, void* user) (index 49)”Lua: node.raw.off
Removes a vehicle-notification subscription previously made with register_vehicle_event. Both cb AND user must match. A dispatch already in flight on the worker thread may still deliver one final call. Inline.
int emit_client_bytes(int id, const char* event, const void* data, int len) (index 108)
Section titled “int emit_client_bytes(int id, const char* event, const void* data, int len) (index 108)”Lua: not exposed
Like emit_client, but the payload is length-delimited and may contain NUL bytes. Event data is opaque on the wire, and Lua resources have always been able to send binary through it; emit_client cannot, because its payload is a C string and stops at the first NUL. Use this one for anything that is not guaranteed text. Returns 0 on success, -1 if the player does not exist.
void emit_all_bytes(const char* event, const void* data, int len) (index 109)
Section titled “void emit_all_bytes(const char* event, const void* data, int len) (index 109)”Lua: not exposed
Like emit_all, but the payload is length-delimited and may contain NUL bytes. See emit_client_bytes.
int emit_others(int except_player_id, const char* event, const char* data) (index 129)
Section titled “int emit_others(int except_player_id, const char* event, const char* data) (index 129)”Lua: node.raw.emitOthers
Sends a named event to every connected client EXCEPT one – the sender of whatever is being relayed, typically. The fan-out is emit_all’s, with the excepted player as the SENDER for the relay filter (relayRequest sees from = except_player_id) and visibility groups. Returns 0, or -1 when except_player_id is not a connected player (nothing is sent: an unknown exception is a bug in the caller, not a broadcast). Inline. ABI 1.8.
Module channel (binary)
Section titled “Module channel (binary)”A raw binary channel keyed by a u32 id, for native modules and their client counterparts that speak their own encoding. Nothing is parsed or logged; use it when JSON events are too slow or too loose. The dimensions module owns channel 0x44494D53 (“DIMS”).
int send_module(int player_id, uint32_t channel, const void* data, int len) (index 98)
Section titled “int send_module(int player_id, uint32_t channel, const void* data, int len) (index 98)”Lua: node.raw.sendModule
Sends one payload on channel: targeted to player_id, or to EVERY synced client when player_id is -1 (the broadcast respects the relayRequest relay filter). Returns 0 on success, -1 when the target player is unknown/not joined or data is NULL with len > 0. Inline.
void register_module_channel(uint32_t channel, NodeModuleChannelCallback cb, void* user) (index 99)
Section titled “void register_module_channel(uint32_t channel, NodeModuleChannelCallback cb, void* user) (index 99)”Lua: node.raw.onModule
Subscribes to inbound client payloads on one channel id (see NodeModuleChannelCallback). Inline.
void unregister_module_channel(uint32_t channel, NodeModuleChannelCallback cb, void* user) (index 100)
Section titled “void unregister_module_channel(uint32_t channel, NodeModuleChannelCallback cb, void* user) (index 100)”Lua: node.raw.offModule
Removes a module-channel subscription. Both cb AND user must match. A dispatch already in flight on the worker thread may still deliver one final call. Inline.
Inter-resource bus
Section titled “Inter-resource bus”Server-side publish/subscribe between resources (and native modules). Delivery is asynchronous on the plugin worker and includes the sender. This is how the chat resource asks the dimensions module which players share a room.
void emit_resource_event(const char* name, const char* data) (index 91)
Section titled “void emit_resource_event(const char* name, const char* data) (index 91)”Lua: node.raw.emitResource
Publishes one named event with an opaque data string to every bus subscriber (Lua node.onResource + C register_resource_event), on the framework worker thread. The C emitter appears as source “native”.
void register_resource_event(const char* name, NodeResourceEventCallback cb, void* user) (index 92)
Section titled “void register_resource_event(const char* name, NodeResourceEventCallback cb, void* user) (index 92)”Lua: node.raw.onResource
Publishes one named event with an opaque data string to every bus subscriber (Lua node.onResource + C register_resource_event), on the framework worker thread. The C emitter appears as source “native”.
void unregister_resource_event(const char* name, NodeResourceEventCallback cb, void* user) (index 93)
Section titled “void unregister_resource_event(const char* name, NodeResourceEventCallback cb, void* user) (index 93)”Lua: node.raw.offResource
Publishes one named event with an opaque data string to every bus subscriber (Lua node.onResource + C register_resource_event), on the framework worker thread. The C emitter appears as source “native”.
Timers and scheduling
Section titled “Timers and scheduling”One-shot and repeating timers on the plugin worker thread – the same thread every handler runs on, so a timer callback never races a handler. setImmediate runs after the current batch of handlers.
int set_timeout(uint32_t ms, NodeTimerCallback cb, void* user) (index 50)
Section titled “int set_timeout(uint32_t ms, NodeTimerCallback cb, void* user) (index 50)”Lua: node.raw.setTimeout
Runs cb once after ms milliseconds, on the framework worker thread. Returns a positive timer id, or -1 if cb is NULL. Pending timers never fire after the framework shuts down. Registration is inline.
int set_interval(uint32_t ms, NodeTimerCallback cb, void* user) (index 51)
Section titled “int set_interval(uint32_t ms, NodeTimerCallback cb, void* user) (index 51)”Lua: node.raw.setInterval
Runs cb every ms milliseconds until cleared. Same semantics as set_timeout otherwise. Returns a positive timer id, or -1.
void clear_timer(int timer_id) (index 52)
Section titled “void clear_timer(int timer_id) (index 52)”Lua: node.raw.clearTimer
Cancels a timer created by set_timeout/set_interval. Safe to call with an already-fired or unknown id (no-op). A callback that is already mid-execution on the worker thread finishes, but an interval will not fire again. Inline.
Coroutines and background jobs
Section titled “Coroutines and background jobs”Two ways to not block the worker. node.async runs a function as a cooperative coroutine: node.sleep and node.wait suspend it while other handlers and the tick keep running (the FiveM Citizen.Wait model). node.job / node.await run a SELF-CONTAINED function on a background pool thread in a scratch Lua state, for real parallelism: no upvalues, no resource globals, JSON-serialisable arguments and result.
int submit_job(NodeJobWorkFn work, NodeJobDoneFn done, void* user) (index 89)
Section titled “int submit_job(NodeJobWorkFn work, NodeJobDoneFn done, void* user) (index 89)”Lua: node.raw.job
Runs work on a background pool thread and hands its return value to done on the framework worker thread (see NodeJobWorkFn). Returns 0, or -1 when work is NULL. done may be NULL (fire-and-forget).
Asynchronous HTTP requests from the background pool; the callback runs on the worker. Any method (GET, POST, PUT, PATCH, DELETE, HEAD, or a custom uppercase token). About 15 s timeout, 8 MB body cap, 5 redirects. TLS peer verification is OFF unless the hoster sets [Http] CaFile in server.toml (env NODE_HTTP_CA_FILE, server 1.2.0): then every https request verifies the chain against that CA bundle and the certificate’s name against the host, and a failure comes back as status -1 with the TLS error in body.
int http_request(const char* method, const char* url, const char* headers_json, const void* body, int body_len, NodeHttpCallback cb, void* user) (index 90)
Section titled “int http_request(const char* method, const char* url, const char* headers_json, const void* body, int body_len, NodeHttpCallback cb, void* user) (index 90)”Lua: node.raw.httpGet
Asynchronous HTTP(S) request from a background pool thread; cb runs on the framework worker thread (see NodeHttpCallback). method is a token of uppercase letters A-Z only, at most 16 (“GET”, “POST”, “PUT”, “PATCH”, “DELETE”, “HEAD”, …; anything else is refused with status -1 “invalid HTTP method” in the callback); headers_json an optional JSON object of extra request headers (NULL = none); body/body_len the optional request body (NULL/0 = none). Follows up to 5 redirects, ~15 s total deadline, 8 MB response cap. TLS does NOT verify the peer certificate unless the hoster set [Http] CaFile in server.toml (server 1.2.0): then the chain is verified against that CA bundle and the name against the host, and a failure is reported like any transport error (status -1, the TLS error text as the body). Returns 0, or -1 on invalid arguments.
Persistent storage
Section titled “Persistent storage”A per-resource JSON key/value store (storage/<resource>.json). Any JSON-serialisable Lua value; every mutation is flushed atomically, so the store survives a restart or a crash.
int storage_set(const char* store, const char* key, const char* value_json) (index 81)
Section titled “int storage_set(const char* store, const char* key, const char* value_json) (index 81)”Lua: node.raw.storageSet
Values are JSON: set takes any JSON value text (a bare string that is not valid JSON is stored as a string), get returns the JSON-encoded value. Stores are named files under storage/ next to server.toml (Lua resources use their resource name; pick any [A-Za-z0-9_.-]+ name, sharing one store with a Lua resource is allowed). Data survives server restarts; every mutation is flushed atomically. set: 0 ok / -1 bad store or key. get: value length / -1 missing or buf too small. delete: 0 removed / -1 not present. All inline. Pass store = NULL for the store belonging to the resource making the call, which is what the scripting form means; name a store explicitly to reach one of your own. A NULL store with no resource in scope – a module calling from a thread of its own – is refused rather than written somewhere arbitrary.
int storage_get(const char* store, const char* key, char* buf, int buflen) (index 82)
Section titled “int storage_get(const char* store, const char* key, char* buf, int buflen) (index 82)”Lua: node.raw.storageGet
Values are JSON: set takes any JSON value text (a bare string that is not valid JSON is stored as a string), get returns the JSON-encoded value. Stores are named files under storage/ next to server.toml (Lua resources use their resource name; pick any [A-Za-z0-9_.-]+ name, sharing one store with a Lua resource is allowed). Data survives server restarts; every mutation is flushed atomically. set: 0 ok / -1 bad store or key. get: value length / -1 missing or buf too small. delete: 0 removed / -1 not present. All inline. Pass store = NULL for the store belonging to the resource making the call, which is what the scripting form means; name a store explicitly to reach one of your own. A NULL store with no resource in scope – a module calling from a thread of its own – is refused rather than written somewhere arbitrary.
int storage_delete(const char* store, const char* key) (index 83)
Section titled “int storage_delete(const char* store, const char* key) (index 83)”Lua: node.raw.storageDelete
Values are JSON: set takes any JSON value text (a bare string that is not valid JSON is stored as a string), get returns the JSON-encoded value. Stores are named files under storage/ next to server.toml (Lua resources use their resource name; pick any [A-Za-z0-9_.-]+ name, sharing one store with a Lua resource is allowed). Data survives server restarts; every mutation is flushed atomically. set: 0 ok / -1 bad store or key. get: value length / -1 missing or buf too small. delete: 0 removed / -1 not present. All inline. Pass store = NULL for the store belonging to the resource making the call, which is what the scripting form means; name a store explicitly to reach one of your own. A NULL store with no resource in scope – a module calling from a thread of its own – is refused rather than written somewhere arbitrary.
Database (PostgreSQL)
Section titled “Database (PostgreSQL)”Asynchronous, pooled access to the PostgreSQL the hoster points the server at ([Database] Url in server.toml, NODE_DATABASE_URL in the environment; empty = disabled). Parameterised statements ($1..$n) and transactions pinned to one connection, submitted from any thread and completed on the framework worker – the Lua thread never blocks on the database. Rows come back keyed by column name with Postgres types mapped to plain values; errors carry the SQLSTATE. The pool connects in the background and reconnects on its own; the server runs without a database and statements then fail with 08001. ABI 1.11; node.pg is the Lua face.
Contract for the server side of the Lua surface (what the prelude relies on): all eleven pg raw names – pgEnabled, pgReady, pgNullSentinel, pgQuery, pgQueryWait, pgTxBegin, pgTxBeginWait, pgTxQuery, pgTxQueryWait, pgTxEnd, pgTxEndWait – are bound unconditionally, database configured or not (the prelude calls raw.pgNullSentinel() at load; one missing binding breaks the prelude for every resource); pgQuery and pgTxQuery answer an unsupported value inside params with the -3 return, never by raising, so that the prelude’s “callback exactly once” holds; pgTxEnd and pgTxEndWait take commit as a Lua boolean; a handle the TxTimeoutMs rollback ended stays addressable until pgTxEnd is called on it, which answers tx_timeout and forgets it.
int pg_enabled(void) (index 145)
Section titled “int pg_enabled(void) (index 145)”Lua: node.raw.pgEnabled
1 iff the server was given a database ([Database] Url in server.toml / NODE_DATABASE_URL is non-empty), so the pg_* entries can queue work; 0 when the driver is off, in which case every pg_* submitter returns -1 at once and no callback runs. Says nothing about whether a connection is up – that is pg_ready. Inline, thread-safe. ABI 1.11.
int pg_ready(void) (index 146)
Section titled “int pg_ready(void) (index 146)”Lua: node.raw.pgReady
1 iff at least one pool connection is established right now; 0 while connecting or reconnecting (and always when pg_enabled is 0). Statements submitted while it is 0 are not held back: they fail at once with SQLSTATE 08001. Connecting happens in the background with exponential retry (0.5 s to 30 s), so the server starts without the database and this turns 1 once it is reached. Inline, thread-safe. ABI 1.11.
int pg_query(const char* sql, const char* params_json, int flags, NodePgCallback cb, void* user) (index 147)
Section titled “int pg_query(const char* sql, const char* params_json, int flags, NodePgCallback cb, void* user) (index 147)”Lua: node.raw.pgQuery
Queues one parameterised statement on the pool; the first free connection runs it and cb runs on the framework worker thread with the outcome (see NodePgCallback). sql uses $1..$n placeholders; params_json is a JSON array of the values in order – null for NULL, true/false, numbers, strings as text, a JSON object or array as its JSON text (for json/jsonb columns) – or NULL/“[]” for none; at most 1000 parameters. flags bit0 = no rows: the result carries count only (INSERT/UPDATE/DELETE without RETURNING). Returns 0 when queued, -1 when the driver is disabled (no [Database] Url), -2 when 1000 statements are already pending, -3 when sql is NULL or params_json is not a JSON array; a non-zero return means nothing was queued and cb never runs. Each statement runs under the connection’s statement_timeout ([Database] QueryTimeoutMs; SQLSTATE 57014 when exceeded); a result over [Database] MaxRows rows fails with code pg_result_cap; a connection lost mid-statement fails with 08006 and the thread reconnects. Autocommit: every statement here is its own transaction – use pg_tx_begin for several statements on one connection. ABI 1.11.
int pg_tx_begin(NodePgTxCallback cb, void* user) (index 148)
Section titled “int pg_tx_begin(NodePgTxCallback cb, void* user) (index 148)”Lua: node.raw.pgTxBegin
Opens a transaction: reserves one pool connection, runs BEGIN on it and calls cb on the framework worker thread with the handle (>= 0) that pg_tx_query and pg_tx_end address it by, or -1 and an error (see NodePgTxCallback). The connection stays reserved – unavailable to pg_query – until pg_tx_end, or until [Database] TxTimeoutMs elapses, when the server rolls back on its own and the next call on the handle fails with code tx_timeout. Returns 0 when queued, -1 when the driver is disabled, -2 when the queue is full; non-zero means cb never runs. Only pg_tx_query runs inside the transaction: a plain pg_query issued meanwhile goes to another connection. ABI 1.11.
int pg_tx_query(int64_t handle, const char* sql, const char* params_json, int flags, NodePgCallback cb, void* user) (index 149)
Section titled “int pg_tx_query(int64_t handle, const char* sql, const char* params_json, int flags, NodePgCallback cb, void* user) (index 149)”Lua: node.raw.pgTxQuery
pg_query on the connection a transaction reserved: the same sql/params_json/flags/cb contract, run in submission order on that connection, inside the open transaction (statements may be queued before the previous one completed). Returns 0 when queued, -1 disabled, -2 queue full, -3 for bad params or an unknown handle – one pg_tx_begin never issued, or one already forgotten by pg_tx_end; non-zero means cb never runs. A handle whose transaction the [Database] TxTimeoutMs rollback ended is still known until pg_tx_end is called on it: pg_tx_query on it queues (returns 0) and cb completes with code tx_timeout. After a failed statement Postgres refuses every further statement of the transaction (25P02) until pg_tx_end rolls it back. ABI 1.11.
int pg_tx_end(int64_t handle, int commit, NodePgCallback cb, void* user) (index 150)
Section titled “int pg_tx_end(int64_t handle, int commit, NodePgCallback cb, void* user) (index 150)”Lua: node.raw.pgTxEnd
Ends a transaction: COMMIT when commit is nonzero, ROLLBACK when 0, then returns the connection to the pool and forgets the handle. cb runs on the framework worker thread with status 0 when the transaction ended as asked (result_json {“rows”:[],“count”:0,“columns”:[]}), or status < 0 with the error – a COMMIT that fails (a deferred constraint, a serialization failure, a lost connection) is rolled back by Postgres, so the transaction is over either way. A handle whose transaction the [Database] TxTimeoutMs rollback already ended is still known: this call queues, cb answers code tx_timeout, and the handle is forgotten – so pg_tx_end is always the last word on a handle. Returns 0 when queued, -1 disabled, -2 queue full, -3 for an unknown handle (never issued by pg_tx_begin, or already ended by pg_tx_end); non-zero means cb never runs. ABI 1.11.
Crypto and JSON
Section titled “Crypto and JSON”SHA-256, HMAC-SHA256, cryptographic random bytes, and JSON encode/decode with a depth guard.
void sha256_hex(const void* data, int len, char out_hex65[65]) (index 94)
Section titled “void sha256_hex(const void* data, int len, char out_hex65[65]) (index 94)”Lua: node.raw.sha256
SHA-256 / HMAC-SHA256 as a lower-case hex string (64 chars + NUL) written into out_hex65. Inline.
void hmac_sha256_hex(const void* key, int key_len, const void* data, int data_len, char out_hex65[65]) (index 95)
Section titled “void hmac_sha256_hex(const void* key, int key_len, const void* data, int data_len, char out_hex65[65]) (index 95)”Lua: node.raw.hmac
SHA-256 / HMAC-SHA256 as a lower-case hex string (64 chars + NUL) written into out_hex65. Inline.
int random_bytes(void* out, int n) (index 96)
Section titled “int random_bytes(void* out, int n) (index 96)”Lua: node.raw.randomBytes
Fills out with n cryptographically random bytes (OpenSSL RAND). Returns 0, or -1 when the generator failed / n is out of 1..65536.
Resources: lifecycle
Section titled “Resources: lifecycle”Reloading a resource by name. The call returns accepted: the reload runs on the worker after the current handler returns, dropping the resource’s registrations, coroutines and state, then loading it again.
int reload_resource(const char* name) (index 119)
Section titled “int reload_resource(const char* name) (index 119)”Lua: node.raw.reloadResource
Queues a reload: the resource is handed back to its language host and loaded again from the same folder. Uniform across runtimes, Lua included, because loading is – the host is not told it is a reload. Everything the resource registered is dropped first, so the new instance never shares the dispatch tables with the old one. QUEUED, not immediate: a reload destroys the resource’s registrations, and the usual place to ask for one is inside a handler, which IS one of those registrations, running. It therefore happens on the worker once the current dispatch finishes. Returns 0 when the request was accepted, -1 when no resource by that name is loaded. A host that then refuses the load leaves the resource unloaded rather than half-loaded, and says so in the log.
int get_resource_manifest_json(const char* resource, char* buf, int buflen) (index 130)
Section titled “int get_resource_manifest_json(const char* resource, char* buf, int buflen) (index 130)”Lua: node.raw.getManifest
The resource’s manifest as JSON: { name, version, type, server: { main }, client: { files (array or null when unlisted), obfuscation (or null) }, config: the [config] table of resource.toml converted as-is (an empty object when absent) }. resource is a resource name, or NULL for the resource making the call. Returns the length written, -1 when there is no such resource or buf is too small; ask with buf = NULL for the size. Read from disk on each call, so a manifest edited while the server runs is seen by the next call. ABI 1.8.
Language hosts and registration ownership
Section titled “Language hosts and registration ownership”Native-module-only: register a NodeLanguageHost so resources of another type (JavaScript, C#) can be loaded, and name the resource a registration belongs to so it is dropped with that resource. Not exposed to Lua.
int register_language_host(const NodeLanguageHost* host) (index 106)
Section titled “int register_language_host(const NodeLanguageHost* host) (index 106)”Lua: not exposed
Register a runtime for a resource type (see NodeLanguageHost). Call from node_plugin_init: modules load before resources, so a host registered there is in place when resources are scanned. Returns 0, or -1 when host/type_name is NULL, struct_size does not match, or that type is already taken (including the built-in “lua”).
int unregister_language_host(const char* type_name) (index 107)
Section titled “int unregister_language_host(const char* type_name) (index 107)”Lua: not exposed
Drop a previously registered host. Its loaded resources are unloaded first, so unload() runs for each while the module is still there. What those resources registered is NOT removed for you – see NodeLanguageHost: undo it in unload(), or the framework will keep calling into a module that is no longer loaded. Returns 0, or -1 when no host owns that type.
void set_resource_owner(const char* resource_name) (index 110)
Section titled “void set_resource_owner(const char* resource_name) (index 110)”Lua: not exposed
Attributes everything registered from THIS THREAD from now on to resource_name, so the framework can drop it when that resource unloads. Pass NULL to go back to registering on your own behalf, which is the default and means the registration lives as long as the module does. Registrations a host makes while the framework is calling its load() are attributed automatically; this is for the ones made later, from an event handler or the host’s own thread. It applies per thread and until changed, so set it, register, and set NULL again.
