OPX//77
Guides

Write a separate resource

Last updated on

How a resource outside opx_infinity uses OPX//77 — server and client exports, public server events, opx_lib — what the player state bag no longer carries, and what it still cannot do.

For a resource that is not part of opx_infinity (your own my_jobs, for example). It uses the curated surface opx_infinity publishes for creators. If you need a module's full contract (every function of character, inventory…), write a module instead.

What a separate resource can use

SurfaceSideUse it forRead
Server exportsserverread a player's data, money, job, items, down state, the roster, the jobs, the catalogue, vehicles, doors; change money, jobs, gangs, duty, your own metadata (online or not), items, stashes, chat, keys, vehicles, doors; register items, use handlers and crafting benches; a toast; a bar the server judges; reviveServer exports
Client exportsclientopen a menu, form or panel, show a toast, run a progress bar, put rows on the eye and hints on the key strip, open your crafting bench, play an animation, read the local player; subscribe to OPX client eventsClient exports
Public server eventsserverreact when a character loads, money moves, an item is used, a car is bought…Server events
Player state bag—nothing since PR #93: never a name to a strangerbelow
opx_libclientinput, markers, zones, RPC helpers…opx_lib
Ordinary commandsserverrunning an unrestricted commandbelow

Start with For creators: the answer every export gives, the allowlist the operator controls (writes are refused until your resource is in SERVER.EXPORTS.WRITERS), and when you must await.

A minimal example

my_jobs/open77.lua
resource "my_jobs"
version "1.0.0"
dependency "opx_infinity"
server_script "server.lua"
client_script "client.lua"
permissions { "network.events" }
my_jobs/client.lua
exports('OnOpxEvent', function(event, payload) TriggerEvent(event, payload) end)

RegisterCommand('jobmenu', function()
	exports.opx_infinity:OpenMenu({
		title = 'ODD JOBS',
		closeOnSelect = true,
		items = { { id = 'deliver', label = 'Deliver a parcel' } },
	})
end)

AddEventHandler('opx:on:menu:action', function(p)
	if p.action == 'select' and p.itemId == 'deliver' then
		TriggerServerEvent('my_jobs:start')
	end
end)
my_jobs/server.lua
-- the operator adds my_jobs = true to SERVER.EXPORTS.WRITERS
RegisterNetEvent('my_jobs:start', function()
	local playerId = source
	CreateThread(function()
		local job = exports.opx_infinity:GetJob(playerId)        -- a read: sync is fine
		if not job.ok then return end
		-- ... run the job, check it on the server, then pay:
		local pending = Open77.exports.call('opx_infinity', 'AddMoney',
			playerId, 'EDDIES', 200, 'odd job')
		local answer = pending and pending:await()
		if answer and answer.ok then
			exports.opx_infinity:SendChat(playerId, { text = 'Paid 200 €$', kind = 'info' })
		end
	end)
end)

AddEventHandler('opx:on:character:unloaded', function(playerId, payload)
	-- forget anything you kept for payload.citizenId
end)

SendChat is a write too, so it needs the same allowlist entry; it answers at once, so the sync form works.

The player state bag

The character module writes no key on the player's replicated state bag any more (PR #93). The old keys (citizenId, name, charInfo, job, gang, username, life) reached every client in the bucket, the whole city, and are cleared. Ask the server instead, and keep the rule the framework keeps: never a name to a stranger — do not relay a character's name, citizen id, job or balance to a client whose player has not met that character.

server — instead of reading the bag
local res = exports.opx_infinity:HasJob(playerId, 'police')
if res.ok and res.value then
	-- ...
end
  • The local player's own character: the client export GetPlayerData.
  • Another player: the server exports (GetPlayerData, HasJob, IsDown…), gated by SERVER.EXPORTS.READ.
  • Platform names outside OPX: Open77.players.nearby and the platform chat/scoreboard still show account names; that is a platform setting.

opx_lib

A client library any resource may use.

my_resource/open77.lua
dependency "opx_lib"
local Lib = require('@opx_lib')
print(Lib.Manifest())   -- the permissions line your manifest needs

Commands

A server resource whose manifest declares runtime.commands can queue an unrestricted command line with ExecuteCommand(line). Restricted (staff) commands are refused, and the answer goes to the server log.

Things to avoid

Do notWhy
Listen to or raise opx:in:* eventsServer TriggerEvent is host-wide, so you can see them, but they are private and change without notice. Use the public events.
Write to opx_infinity's tables directlyIt bypasses its rules, hooks and audit, and its in-memory copy overwrites your change. Read-only access is possible with database.access.
Trust a clientThe client exports act on one player's screen. Charge, give and check on the server.

What you still cannot do

You want toStatus
Veto an action (hooks)OPX.Hooks runs inside opx_infinity only: a veto answers inside the action, synchronously, and a call into another resource cannot be bounded there.
Hear any OPX client eventOnly the events on the Subscribe list.
Open an impound or garage screen for a player, or gate a garage on a jobNo export yet.
Run an item shop of the shops moduleshops is the clothing shop. Sell with your own menu and RemoveMoney + AddItem, or a crafting bench.
Register a weapon at runtimeRegisterItem refuses weapons and ammunition: they need a TweakDB record and a class from data/weapons.lua.

What used to be on this list and is not any more: the panel, the key strip and the eye (client exports), a bar the server starts and judges (StartProgress), an offline character's metadata (SetMetadata with a citizen id), runtime items and use handlers (RegisterItem, RegisterUsableItem).

A request/answer between your client and your server is Lib.Callback in opx_lib, over Open77.net: it is between your own two halves and needs no export.

Code written for the old opx77_* exports does not work as is; see From opx77_* to opx_infinity.

On this page