OPX//77
Creators

Events

Last updated on

The public opx:on:* server events any resource can listen to, the private opx:in:* events, and the client side.

On the server, TriggerEvent reaches every running resource. opx_infinity announces what happens on opx:on:<module>:<verb> names raised through OPX.Publish. Any server resource can listen; no allowlist applies.

my_logs/server/main.lua
AddEventHandler('opx:on:character:money', function(playerId, payload)
	print(('%s %s %d %s (now %s)'):format(payload.citizenId, payload.action,
		payload.amount, payload.moneyType, tostring(payload.balance)))
end)

Every event has two arguments:

  • playerId: number? — the player the event is about; nil when that character is not online
  • payload: table — built for the event; never a live record, never the free-form metadata

The events are announcements: they fire after the change, and nothing a listener does can undo or veto it.

Advisory, not proof

Server TriggerEvent is host-wide in both directions: any server resource can raise an opx:on:* name with any payload. Use these events to refresh, log or react — never as proof that something happened. Before you pay, give or unlock on the strength of one, ask OPX again (a read export such as GetMoney, CountItem or GetVehicle). For a server-started bar, keep the id StartProgress answered and check owner too.

opx:on:character:loaded

Server, host-wide. A character was loaded for a player.

AddEventHandler('opx:on:character:loaded', function(playerId, payload) end)
  • payload: table — the public view
    • source, citizenId, userId, firstName, lastName, money, job, gang, jobs, gangs

opx:on:character:unloaded

Server, host-wide. A character left the world (logout, switch, disconnect).

AddEventHandler('opx:on:character:unloaded', function(playerId, payload) end)
  • payload: { citizenId, userId }

opx:on:character:created

Server, host-wide. A character row was made (no name and no body yet: those come in the world).

AddEventHandler('opx:on:character:created', function(playerId, payload) end)
  • payload: { citizenId, userId, slot }

opx:on:character:deleted

Server, host-wide. A character was deleted (soft: the row stays, the slot is freed). Forget whatever you keep for this citizen id.

AddEventHandler('opx:on:character:deleted', function(playerId, payload) end)
  • playerId?: number — set when the character was being played
  • payload: { citizenId, userId, by } — by is the connection that asked (the player, staff), nil for the console

opx:on:character:money

Server, host-wide. Any balance change.

AddEventHandler('opx:on:character:money', function(playerId, payload) end)
  • payload: table
    • citizenId: string
    • moneyType: string
    • amount: integer
    • action: 'add' | 'remove' | 'set'
    • reason?: string
    • balance: integer?
    • offline: boolean — true (and playerId nil) for AddMoneyOffline

opx:on:character:job

Server, host-wide. The primary job was set, its duty changed, or a job membership was removed.

AddEventHandler('opx:on:character:job', function(playerId, payload) end)
  • payload: table
    • citizenId: string
    • job: table — the primary job after the change
    • removed?: string — the job removed
    • offline: boolean — the change was made to a character nobody is playing

opx:on:character:gang

Server, host-wide. The same for gangs.

AddEventHandler('opx:on:character:gang', function(playerId, payload) end)
  • payload: { citizenId, gang, removed?, offline }

opx:on:inventory:changed

Server, host-wide. A container's contents were written.

AddEventHandler('opx:on:inventory:changed', function(playerId, payload) end)
  • payload: table
    • kind, owner, container
    • citizenId?: string — only for a player's bag (and then playerId is set)

opx:on:inventory:used

Server, host-wide. A player used an item (already consumed; nothing can decide the use from here).

AddEventHandler('opx:on:inventory:used', function(playerId, payload) end)
  • payload: { citizenId, name, slot, consumed, metadata }

opx:on:inventory:items

Server, host-wide. What a player's bag gained and lost, by item name: a give (raised when the receiver accepts; an offer that is refused or expires moves nothing and raises nothing), a pickup, a drop, a craft, a use, an export call. A drag inside the bag moves nothing in or out and raises nothing.

AddEventHandler('opx:on:inventory:items', function(playerId, payload)
  for _, change in ipairs(payload.changes) do
    if change.name == 'lockpick' and change.delta > 0 then print('got a lockpick') end
  end
end)
  • payload: table
    • citizenId: string
    • changes: { name, delta, count }[] — delta the units that moved (negative for a loss), count what the bag holds now; sorted by name

opx:on:downed:changed

Server, host-wide. A player went down or got up.

AddEventHandler('opx:on:downed:changed', function(playerId, payload) end)
  • payload when down: { citizenId, down = true, waiting, restored }
    • restored: boolean — the down state came back after a reconnect
  • payload when up: { citizenId, down = false, reason, kept }
    • kept: boolean — the character left the world still down; the state returns with them

opx:on:needs:changed

Server, host-wide. A player's needs moved on the server: decay, an item used, staff, or an export.

AddEventHandler('opx:on:needs:changed', function(playerId, payload) end)
  • payload: table
    • citizenId: string
    • values: table — every need after the change
    • changed: string[] — the keys that moved, sorted
    • source: string — decay, use, staff, set, add or ext:<resource> (an AddNeeds / SetNeeds call)

The first load of a character raises nothing here; read it with GetNeeds.

opx:on:vehicles:spawned

Server, host-wide. An owned vehicle was brought out.

AddEventHandler('opx:on:vehicles:spawned', function(playerId, payload) end)
  • payload: table
    • citizenId, plate, record, vehicleId
    • vehicleKey: string — the engine id as text (safe in JSON)
    • recalled?: true — it was already out and was moved

opx:on:vehicles:stored

Server, host-wide. It was put away, or the host removed it from the world.

AddEventHandler('opx:on:vehicles:stored', function(playerId, payload) end)
  • payload: { citizenId, plate, garage }, or { citizenId, plate, removed } — removed says why the host took it away

opx:on:vehicles:registered

Server, host-wide. A vehicle row was made for a character: bought at a dealer, given by AddVehicle or a staff command.

AddEventHandler('opx:on:vehicles:registered', function(playerId, payload) end)
  • payload: { citizenId, plate, record, garage }

opx:on:vehicles:state

Server, host-wide. A vehicle was moved to stored or impounded (SetVehicleState, the impound door).

AddEventHandler('opx:on:vehicles:state', function(playerId, payload) end)
  • payload: { citizenId, plate, state, garage } — state is 'stored' or 'impounded'

opx:on:dealership:sold

Server, host-wide. A vehicle was bought at a dealer (counter) or sold player-to-player (offer).

AddEventHandler('opx:on:dealership:sold', function(playerId, payload) end)
  • payload (counter): { kind = 'counter', citizenId, plate, entry, record, dealer, garage, price, currency }
  • payload (offer): { kind = 'offer', citizenId, plate, entry, record, dealer, garage, price, currency, seller, sellerCitizenId, company, commission, banked, pending }
    • citizenId is the buyer; seller / sellerCitizenId the salesperson.
    • garage?: string — the buyer's chosen garage; nil = the vehicles module default
    • company: string — <kind>:<group>
    • commission: integer — paid to the seller (0 if it could not be paid; it then went to the company)
    • banked: integer — the company's share
    • pending: boolean — true when the company share went to the pending ledger to be paid in later; the sale is done either way

opx:on:hauling:sold

Server, host-wide. Crates were sold to a drop-off NPC.

AddEventHandler('opx:on:hauling:sold', function(playerId, payload) end)
  • payload: { citizenId, site, dropoff, count, pay, each, currency }

opx:on:doorlock:changed

Server, host-wide. A door was locked or unlocked — by a player, staff, autolock or an export.

AddEventHandler('opx:on:doorlock:changed', function(playerId, payload) end)
  • playerId: number? — nil when no player turned it (autolock, an export)
  • payload: table
    • id, door: integer — the door id (the same value twice)
    • name: string
    • state: 0 | 1 — 1 locked
    • locked: boolean
    • by: string — the player id, lockpick:<id>, staff:<id>, autolock or the contract caller
    • item?: string — the key item that opened it, if any

See doorlock.

opx:on:fuel:changed

Server, host-wide. A tank moved for any reason but driving: a refuel, a can, staff, an export, or a tank that just ran empty.

AddEventHandler('opx:on:fuel:changed', function(playerId, payload) end)
  • playerId: number? — who poured, the driver for empty, the staff member; nil for an export
  • payload: table
    • vehicleId: integer — the live vehicle id
    • vehicleKey: string — the same id as text (exact above 2^53)
    • litres: number — the tank now, in litres
    • previous?: number — before, in litres
    • percent: number — 0..100
    • capacity: number — litres in a full tank
    • reason: 'refuel' | 'can' | 'staff' | 'empty' | 'ext:<resource>'

Driving is not announced: it moves every tank every second, and the bag replicates it. See fuel.

Example
AddEventHandler('opx:on:fuel:changed', function(playerId, payload)
  if payload.reason == 'refuel' then
    print(('%s filled %s to %d%%'):format(tostring(playerId), payload.vehicleKey, math.floor(payload.percent)))
  end
end)

opx:on:crafting:ordered

Server, host-wide. A player placed an order at a bench: materials and price already taken.

AddEventHandler('opx:on:crafting:ordered', function(playerId, payload) end)
  • payload: { citizenId, bench, recipe, item, count, seconds, price, money?, order }

opx:on:crafting:collected

Server, host-wide. A finished order was handed over.

AddEventHandler('opx:on:crafting:collected', function(playerId, payload) end)
  • payload: { citizenId, bench, recipe, item, count, order }

opx:on:progress:finished

Server, host-wide. A bar the server started ended, as the server decided it: StartProgress.

AddEventHandler('opx:on:progress:finished', function(playerId, payload) end)
  • payload: table
    • id: integer — the id StartProgress answered
    • owner: string — the resource (or module) that started it; filter on your own name
    • ending: string — finished, stopped, cancelled, interrupted, refused (the client could not draw it), rejected (finished sooner than its duration), no_answer, left
    • completed: boolean — only true means the action happened
    • elapsedMs: integer — by the server's clock

The private opx:in:* events

Server opx:in:* events are visible to every server resource

Because server TriggerEvent is host-wide, the internal opx:in:* events (for example opx:in:character:loaded) also reach your resource, and your resource could raise them. They are not part of the public surface: their names and arguments change without notice. Listen to the opx:on:* events above instead, and never raise an opx: event yourself.

On the client

The client-side opx:on:* events (menu actions, inventory opened…) stay inside opx_infinity: a client TriggerEvent does not leave its resource. Answers to your own client calls reach you through the reply export, and some client events can be forwarded with Subscribe.

On this page