Client exports
Last updated on
Put OPX menus, forms, panels, toasts, progress bars, eye rows, key hints and animations on the local player's screen from your own client script, read the local player, and hear OPX client events.
Defined in core/client/exports.lua. They answer { ok, value | error } like the server exports. Callers allowed: CLIENT.EXPORTS.CALLERS ('*', everyone, by default). None of them yields, so the synchronous form is fine:
local answer = exports.opx_infinity:ShowToast({ message = 'Order ready', kind = 'success' })Everything here acts on this player's screen only. The server must still check anything that matters.
Getting answers back: the reply export
A function cannot be passed to another resource, and a client TriggerEvent does not leave opx_infinity, so an answer is delivered by calling an export of yours. Publish this once:
exports('OnOpxEvent', function(event, payload)
TriggerEvent(event, payload) -- re-raise it on your own resource's bus
end)Every answer then arrives in your resource as an ordinary event:
| Event (on your bus) | Sent after | Payload |
|---|---|---|
opx:on:menu:action | every action on a menu you opened | the menu's action payload |
opx:on:form:answer | a form you opened is submitted or cancelled | the form's answer |
opx:on:panel:action | every action on a panel you opened | the panel's action payload, owner your resource |
opx:on:progress:done | a bar you started ends | { owner, label, ending, finished }; only finished = true means the action completed |
opx:on:animations:result | the server's verdict on an animation you asked for | { requestId, action, ok, error?, animation?, variant?, playbackId?, ... } |
- In each payload
owneris your resource name. - The export is
CLIENT.EXPORTS.REPLY(OnOpxEvent) unless the call names another withreply(letters, digits,_, max 64). - If your resource does not publish it, the answer is lost and the client log says
[exports] <you> did not take <event> through its OnOpxEvent export: ....
The exports
| Export | Does |
|---|---|
OpenMenu | Opens a menu |
UpdateMenu | Patches your open menu |
CloseMenu | Closes your menu |
OpenForm | Opens a form |
CloseForm | Cancels your form |
ShowToast | Shows a toast |
DismissToast | Removes your toast |
StartProgress | Runs a progress bar |
StopProgress | Stops your bar |
PlayAnimation | Asks for an emote on the local player |
StopAnimation | Stops your animation |
Subscribe | Forwards an OPX client event to you |
Unsubscribe | Stops forwarding it |
OpenPanel | Opens the side panel |
UpdatePanel, AppendPanel, ClosePanel | Patch, fill and close your panel |
AddTarget | Puts rows on the target eye |
UpdateTarget, RemoveTarget, ClearTargets | Change and remove your rows |
ShowPrompt | Puts key hints on the key strip |
UpdatePrompt, HidePrompt, HideAllPrompts | Change and take down your hints |
OpenCraftingBench | Opens one of your crafting benches |
GetPlayerData, GetItemCount, HasItem, GetItem, IsDown, GetNeeds | Read the local player |
Errors common to all: export.callerDenied, export.badArgument, error.unavailable (the module is not running), stale_handle, and the codes of the module behind the export (for example menu_busy, form_busy, progress_busy, or a spec error such as invalid_items).
Subscribing to OPX client events
Your resource cannot hear opx_infinity's client events directly. Subscribe forwards one to your reply export every time it is raised, until you unsubscribe or your resource stops. Each arrives as (event, payload) with one table:
| Event | Payload |
|---|---|
opx:on:character:loaded | the character's PlayerData without metadata |
opx:on:character:unloaded | {} |
opx:on:character:changed | PlayerData without metadata |
opx:on:character:money | { moneyType, amount, action, balance } (one table, not four arguments) |
opx:on:character:job | the primary job table |
opx:on:character:gang | the primary gang table |
opx:on:downed:changed | { down, waiting } |
opx:on:inventory:changed | { inventory, changes } (see inventory) |
opx:on:inventory:used | { name, slot, label, close, status?, animation? } |
opx:on:inventory:opened | {} |
opx:on:inventory:closed | {} |
opx:on:needs:changed | { values, changed, source, citizenId, ready } — source is loaded, the server's reason (decay, use, staff, ext:<resource>…) or unloaded |
opx:on:progress:state | { open, owner?, label? }. owner is your resource name for your bar; a bar drawn by another resource has no owner. |
No other event can be subscribed to (export.notSubscribable).
exports('OnOpxEvent', function(event, payload) TriggerEvent(event, payload) end)
CreateThread(function()
exports.opx_infinity:Subscribe('opx:on:character:money')
end)
AddEventHandler('opx:on:character:money', function(p)
print(('balance %s is now %d'):format(p.moneyType, p.balance))
end)Ownership
- Your screens are kept under the owner
ext:<your resource>. You can update, close or stop only what you opened; you never take over another owner's menu or form. - When your resource stops, OPX closes your menus (reason
owner_stopped), forms and panel, stops your bar and your animation, removes your eye rows and key hints, and drops your subscriptions. - Eye rows and key hints are kept under your resource name (the eye watches its generation and drops them on a reload). A resource named like a module of OPX (
target,inventory, ...) is refused them:export.ownerTaken.
Example: a shop menu
exports('OnOpxEvent', function(event, payload) TriggerEvent(event, payload) end)
local function openShop()
local answer = exports.opx_infinity:OpenMenu({
title = 'NIGHT MARKET',
closeOnSelect = true,
items = {
{ id = 'water', label = 'Water', value = '5 €$' },
{ id = 'burrito', label = 'Burrito', value = '12 €$' },
},
})
if not answer.ok then print('menu refused: ' .. answer.error) end
end
AddEventHandler('opx:on:menu:action', function(p)
if p.action == 'select' and p.itemId then
-- ask YOUR server to sell it; the server checks and charges
TriggerServerEvent('my_shop:buy', p.itemId)
end
end)On the server, my_shop:buy re-checks the item and the player, then charges with RemoveMoney and gives with AddItem, both awaited.