Shared library (lib/)
Last updated on
Every helper in opx_infinity's lib/ directory — results, text, numbers, validation, hooks, locales, citizen ids, job gates, placed spots, storage, audit logging and the WebUI surface.
lib/ holds the plain helpers every module uses. They load before any module, on the side shown, and like the rest of OPX are only reachable from code inside opx_infinity. A separate resource can use the client-side opx_lib instead — a different library. Runtime functions (OPX.Modules, OPX.Scheduler, OPX.Refuse …) are under Core.
Result
lib/shared/result.lua, both sides. A Result is { ok = true, value = v } or { ok = false, error = code, detail = text? }: error is a stable code to branch on, detail is for the log only (Result convention).
OPX.Result.Ok
OPX.Result.Ok(value)Parameters
- value?:
any— may benil
Returns
{ ok = true, value }
OPX.Result.Err
OPX.Result.Err(code, detail)Parameters
- code:
string - detail?:
string
Returns
{ ok = false, error, detail }
if not row then return OPX.Result.Err('not_found') end
return OPX.Result.Ok(row)OPX.Result.Kind
The toast kind a refusal code's situation calls for. The code is lower-cased and stripped to letters, then matched: toofast, ratelimit, busy → warning (wait, and it works); toofar, notallowed, notpermitted, nopermission → error (not from here, or not for you). Anything else answers fallback. OPX.Refuse, OPX.NotifyLocale and OPX.Toast.Locale apply it.
OPX.Result.Kind(code, fallback)Parameters
- code:
any— a refusal code or catalogue key - fallback:
string
Returns
string
OPX.Result.Kind('error.tooFast', 'error') -- 'warning'
OPX.Result.Kind('garages.tooFar', 'warning') -- 'error'
OPX.Result.Kind('shops.noStock', 'error') -- 'error' (the fallback)Table
lib/shared/table.lua, both sides.
OPX.Table.DeepCopy
Copies keys and values; safe on self-referencing tables.
OPX.Table.DeepCopy(source, seen)Parameters
- source:
any - seen?:
table— internal
Returns
- the copy
OPX.Table.Count
Every key, array part included.
OPX.Table.Count(source)Parameters
- source:
table
Returns
integer
String
lib/shared/string.lua, both sides.
OPX.String.Length
Length in characters. Bytes when the utf8 library is missing.
OPX.String.Length(text)Parameters
- text:
string
Returns
integer?—nilif not valid UTF-8
OPX.String.Trim
Removes leading and trailing whitespace, in linear time.
OPX.String.Trim(text)Parameters
- text:
string
Returns
string
OPX.String.Interpolate
Replaces {name} with params.name. Unknown names stay as written.
OPX.String.Interpolate(text, params)Parameters
- text:
string - params?:
table
Returns
string
OPX.String.Interpolate('Hello {name}', { name = 'V' }) -- 'Hello V'OPX.String.Random
A → random letter, 1 → random digit, . → either; other characters are copied.
OPX.String.Random(template)Parameters
- template:
string
Returns
string
OPX.String.Random('AAA-111') -- e.g. 'KQZ-482'Math
lib/shared/math.lua, both sides.
OPX.Math.Clamp
OPX.Math.Clamp(value, low, high)Parameters
- value, low, high:
number
Returns
number
OPX.Math.IsFinite
true only for a number that is not NaN or ±infinity.
OPX.Math.IsFinite(value)Parameters
- value:
any
Returns
boolean
OPX.Math.Finite
tonumber(value) if finite. No size cap (unlike OPX.Text.Finite).
OPX.Math.Finite(value)Parameters
- value:
any
Returns
number?
OPX.Math.DistanceSquared
Squared distance; a missing z counts as 0.
OPX.Math.DistanceSquared(a, b)Parameters
- a, b:
{ x, y, z? }
Returns
number
OPX.Math.GroupDigits
Whole part grouped by thousands.
OPX.Math.GroupDigits(value, separator)Parameters
- value:
number - separator?:
string— default a space
Returns
string
OPX.Math.GroupDigits(1234567) -- '1 234 567'Text
lib/shared/text.lua, both sides. Display text, measured in characters, never split inside a UTF-8 character.
OPX.Text.Span
Byte length of the first maximum characters.
OPX.Text.Span(text, maximum)Parameters
- text:
string - maximum:
integer
Returns
integer
OPX.Text.Clean
Replaces control characters with spaces, mends broken UTF-8 and cuts to maximum characters, adding ellipsis if cut. Numbers are converted. The answer is always valid UTF-8: each byte that does not start a valid sequence becomes ? (valid text costs one native utf8.len); after 16 repairs the rest is dropped.
OPX.Text.Clean(value, maximum, ellipsis)Parameters
- value:
any - maximum:
integer - ellipsis?:
string
Returns
string?—nilfor a non-string, non-number
OPX.Text.Bytes
Cuts to at most limit bytes on a character boundary.
OPX.Text.Bytes(text, limit)Parameters
- text:
string - limit:
integer
Returns
string
OPX.Text.Finite
A finite number within ±2^53, else nil.
OPX.Text.Finite(value)Parameters
- value:
any
Returns
number?
OPX.Text.Integer
Finite, and whole.
OPX.Text.Integer(value)Parameters
- value:
any
Returns
integer?
OPX.Text.Rest
Joins command arguments from index first with spaces. Uses args.n when set.
OPX.Text.Rest(args, first)Parameters
- args:
string[] - first:
integer
Returns
string?—nilwhen nothing is left
local message = OPX.Text.Rest(args, 2) -- '/opx.thing 12 hello there' -> 'hello there'OPX.Text.Switch
Reads an on/off argument.
OPX.Text.Switch(value)Parameters
- value:
any
Returns
boolean?—on/true/1/yes→true,off/false/0/no→false,nil→nil'invalid'?— anything else answersnil, 'invalid'
OPX.Text.Slug
Lower-cased; 1–32 characters of letters, digits, _ and -, else nil.
OPX.Text.Slug(value)Parameters
- value:
any
Returns
string?
Validate
lib/shared/validate.lua, both sides. For values that came from a client. Both answer a Result.
OPX.Validate.Text
Trims text and checks its length in characters and its pattern. Input longer than max × 4 + 16 bytes (at most 1024) is refused before any work.
OPX.Validate.Text(value, opts)Parameters
- value:
any - opts?:
table- min?:
integer— default 1 - max?:
integer— default 255 - pattern?:
string
- min?:
Returns
Result— value is the trimmed text; errorstype,too-long,not-utf8,too-short,format
local name = OPX.Validate.Text(payload.name, { min = 2, max = 32 })
if not name.ok then return OPX.Refuse(source, 'error.badRequest', 'thing.rename') endOPX.Validate.Number
OPX.Validate.Number(value, opts)Parameters
- value:
any - opts?:
table- integer?:
boolean - min?:
number - max?:
number
- integer?:
Returns
Result— value is the number; errorstype,not-finite,not-integer,too-small,too-large
Hooks
lib/shared/hooks.lua, both sides. Named points where code inside opx_infinity can veto an action.
OPX.Hooks.Register
Lower priority runs first; equal priorities keep registration order. Raises if name is not a string or fn not a function.
OPX.Hooks.Register(name, fn, priority)Parameters
- name:
string - fn:
function(payload)— return exactlyfalseto veto - priority?:
number
Returns
integer— id
OPX.Hooks.Register('money:beforeAdd', function(p)
if p.amount > 1000000 then return false end
end)OPX.Hooks.Remove
Also stops a trigger already in progress from calling it.
OPX.Hooks.Remove(id)Parameters
- id:
integer
Returns
boolean— removed
OPX.Hooks.Trigger
Runs every hook in order. Stops and answers false at the first hook that returns exactly false. A hook that raises is logged and skipped.
OPX.Hooks.Trigger(name, payload)Parameters
- name:
string - payload:
table
Returns
boolean— allowed;truewhen no hook is registered
Hook points
Raised by the shipped modules (server side, all in character). Payload fields and refusal codes: character.
| Name | Payload | A false does |
|---|---|---|
money:beforeAdd | { player, moneyType, amount, reason } | refuses the credit |
money:beforeRemove | { player, moneyType, amount, reason } | refuses the debit |
money:beforeSet | { player, moneyType, amount, reason } | refuses the change |
paycheck:before | { player, amount } | skips that player's paycheck |
character:loading | { citizenId, entity, data = {} } | nothing: the result is not read. Hooks fill data (the appearance module puts clothing there). |
Locale
lib/shared/locale.lua, both sides. Language catalogues; English (en) is always the fallback. The active language comes from LOCALE. The global locale(key, params) is OPX.Locale.Text.
OPX.Locale.Register
Merges keys into that language; existing keys are replaced.
OPX.Locale.Register(code, strings)Parameters
- code:
string - strings:
table<key, text>
OPX.Locale.Set
Changes the active language.
OPX.Locale.Set(code)Parameters
- code:
string
Returns
boolean—falsefor a non-string or empty code
OPX.Locale.Current
The active language code.
OPX.Locale.Current()Returns
string
OPX.Locale.Exists
In the active or the fallback catalogue.
OPX.Locale.Exists(key)Parameters
- key:
string
Returns
boolean
OPX.Locale.Text
Active language, then English, then the key itself. {name} placeholders are filled from params.
OPX.Locale.Text(key, params)Parameters
- key:
string - params?:
table
Returns
string
locale('thing.done', { count = 3 })OPX.Locale.Catalogue
A flat copy: English with the active language on top. Sent to the WebUI page.
OPX.Locale.Catalogue()Returns
table<key, text>
OPX.Locale.CatalogueSliced
The same flat copy, built a slice at a time: it yields (Wait(0)) every every keys, so copying several thousand keys on a client does not overrun one resume's instruction budget. Call it from a thread. The client runtime uses it on opx:ready.
OPX.Locale.CatalogueSliced(every)Parameters
- every:
integer— keys copied between two yields
Returns
table<key, text>
CreateThread(function()
local strings = OPX.Locale.CatalogueSliced(200) -- one Wait(0) per 200 keys
print(OPX.Table.Count(strings))
end)OPX.Locale.Money
An amount of money as a player reads it, in the active language: rounded, grouped with money.separator (, in English, a narrow no-break space in French), with the currency word money.type.<TYPE> (€$, €$ (bank)), through money.amount. An unknown type is shown by its configured name.
OPX.Locale.Money(amount, moneyType)Parameters
- amount:
number - moneyType?:
string— a configured money type; defaultSHARED.MONEY.DEFAULT, else'EDDIES'
Returns
string
OPX.Locale.Money(4500) -- '4,500 €$' (en), '4 500 €$' (fr)
OPX.Locale.Money(500, 'BANK') -- '500 €$ (bank)'CitizenId
lib/shared/citizenid.lua, both sides. A citizen id is seven symbols from 34679ACDEFGHJKMNPRTWXYZ, written XXX-XXXX. The seventh is a check symbol (weighted sum modulo 23), so one wrong symbol or two swapped neighbours are caught. OPX.CitizenId.ALPHABET holds the symbol set; changing it invalidates every id already issued.
OPX.CitizenId.Build
Builds the id from six values (each taken modulo 23) and adds the check symbol.
OPX.CitizenId.Build(values)Parameters
- values:
integer[]— six
Returns
string
OPX.CitizenId.Generate
A random id. Does not check the database for collisions.
OPX.CitizenId.Generate(rng)Parameters
- rng?:
function(low, high)— defaultmath.random
Returns
string
OPX.CitizenId.Parse
Case-insensitive; spaces, - and _ are ignored.
OPX.CitizenId.Parse(input)Parameters
- input:
any
Returns
Result— value is the grouped id; errorstype,length(more than 32 bytes, or not 7 symbols),alphabet,checksum
local id = OPX.CitizenId.Parse('k7m 4 dxw')
if id.ok then print(id.value) end -- 'K7M-4DXW' if the check symbol matchesAnchors
lib/shared/anchors.lua, both sides. One set of screen positions for floating surfaces. OPX.Anchors.ALL: top-left, top-center, top-right, left, center, right, bottom-left, bottom-center, bottom-right. Not every module uses this set yet; check each module's ANCHOR key.
OPX.Anchors.Resolve
value if it is one of the nine positions, else fallback (or 'center' if fallback is not valid either). A wrong non-nil value is logged, naming where.
OPX.Anchors.Resolve(value, fallback, where)Parameters
- value:
any - fallback:
string - where:
string
Returns
string
JobGate
lib/shared/jobgate.lua, both sides. Decides whether a character may use something gated by job. Used by elevators, gunsmith, hauling, teleports and blips; shops asks the character module instead.
The JOBS format
JOBS = { ncpd = 2, medic = 0 }, -- job name -> minimum grade level
ON_DUTY = true, -- optional: must be on duty in that job- One matching job is enough. A minimum of
0means "anyone holding the job". - No
JOBS, or an empty one, means public: always open, even with no character data. - A minimum that is not a number never matches (the gate stays shut), and
Problemsreports it.
The module turns this into a requirement { jobs = JOBS, onDuty = ON_DUTY } and passes a snapshot of the character:
{ job = { name = 'ncpd', grade = { level = 2 }, onDuty = true },
jobs = { ncpd = 2, fixer = 0 }, -- every membership, grade only
atMs = OPX.Now() } -- when the snapshot was readOPX.JobGate.Evaluate
A gated requirement fails on any doubt.
OPX.JobGate.Evaluate(requirement, snapshot, nowMs, policy)Parameters
- requirement:
{ jobs, onDuty? } - snapshot:
table? - nowMs:
integer - policy?:
table- maxAgeMs?:
integer— default 0 - membership?:
'primary' | 'any'— default'primary': only the worked job counts;'any'also counts other memberships for the grade (never for on-duty)
- maxAgeMs?:
Returns
- allowed:
boolean - code?:
string— nearest miss first:off_duty,grade_too_low,job_required; alsono_characterandjob_stale(snapshot older thanmaxAgeMs, from the future, or a clock that cannot be read)
local ok, code = OPX.JobGate.Evaluate({ jobs = row.JOBS, onDuty = row.ON_DUTY }, snapshot, OPX.Now())
if not ok then return OPX.Refuse(source, 'thing.' .. code) endOPX.JobGate.Problems
Appends one line per malformed JOBS entry (not a table, or a name/grade that is not string → number). Job names are not checked against the character config.
OPX.JobGate.Problems(jobs, where, lines)Parameters
- jobs:
any - where:
string - lines?:
string[]
Returns
string[]
Spots
lib/shared/spots.lua, both sides. The shared format for places an operator writes down as coordinates and a player walks to. Used by garages, dealership and clothing (full record), and by teleports, elevators, gunsmith and hauling (coordinate box and marker look). OPX.Spots.SHAPES and OPX.Spots.STYLES hold the two allowed sets.
The spot format
SPOTS = {
watson_kabuki = { KIND = 'garage', LABEL = 'Kabuki', X = -1442.2, Y = 127.4, Z = 18.0,
HEADING = 90.0, BUCKET = 0 },
}| Field | Rule |
|---|---|
| key | String, 1 to spec.maxKey characters. |
KIND | Required only when the module has kinds; one of spec.kinds, case-insensitive. |
LABEL | Optional string; defaults to the key. |
X, Y, Z | Finite numbers within ±1 000 000 (OPX.Spots.BOUND). |
HEADING | Only for modules whose spec has heading; finite number, default 0.0. |
BUCKET | Whole number >= 0, default 0. |
- A spot that breaks a rule is refused with a message and skipped.
- On the wire (server to client) the same record uses lower-case fields:
key, label, kind, x, y, z, heading, bucket. - A module's spec says how its spots differ:
{ noun, maxKey, kinds?, kindNames?, heading? }.nounis the word used in refusals ('spot','dealer','store').
The marker format
MARKER = { shape = 'ring', style = 'interaction', RADIUS = 1.5 },
MAX_DISTANCE = 60, -- metres a marker is drawn from
GROUND_OFFSET = 0.06, -- metres the marker sits above the floor| Field | Allowed | Bad value |
|---|---|---|
shape | ring, cylinder | the module's default shape |
style | interaction, objective, spawn, danger | the module's default style |
RADIUS | 0.1–50 | the module's default radius |
MAX_DISTANCE | 1–500 m | the module's default |
GROUND_OFFSET | 0–2 m | 0.06 |
Each field falls back on its own, so one typo does not reset the others.
OPX.Spots.Coordinate
Finite and within BOUND.
OPX.Spots.Coordinate(value)Returns
number?
OPX.Spots.Integer
A whole Coordinate.
OPX.Spots.Integer(value)Returns
integer?
OPX.Spots.Marker
Resolves the marker look per field.
OPX.Spots.Marker(declared, fallback, groundOffset)Parameters
- declared:
{ shape, style, RADIUS }? - fallback:
{ shape, style, RADIUS } - groundOffset?:
number
Returns
{ shape, style, radius, lift }
OPX.Spots.MaxDistance
value if 1–500, else fallback.
OPX.Spots.MaxDistance(value, fallback)Returns
number
OPX.Spots.MarkerProblems
Appends what is wrong with a marker block.
OPX.Spots.MarkerProblems(declared, label, lines)Parameters
- declared:
table? - label:
string— e.g.MARKER.avpad - lines:
string[]
Returns
string[]
OPX.Spots.DrawProblems
Appends what is wrong with MAX_DISTANCE and GROUND_OFFSET.
OPX.Spots.DrawProblems(maxDistance, groundOffset, lines)Returns
string[]
OPX.Spots.FromDefinition
One config or database row (upper-case fields).
OPX.Spots.FromDefinition(spec, key, raw)Returns
- spot:
table? - why?:
string
OPX.Spots.FromWire
One wire record (lower-case fields).
OPX.Spots.FromWire(spec, raw)Returns
- spot:
table? - why?:
string
OPX.Spots.Serialise
The wire record.
OPX.Spots.Serialise(spot)Returns
table
OPX.Spots.Coerce
Builds every spot; refusals are appended to problems.
OPX.Spots.Coerce(spec, definitions, problems)Parameters
- spec:
table - definitions:
table<key, table> - problems?:
string[]
Returns
table<key, spot>
local problems = {}
local spots = OPX.Spots.Coerce({ noun = 'spot', maxKey = 32, heading = true }, M.Settings.SPOTS, problems)
for _, line in ipairs(problems) do Open77.log.warn(line) endOPX.Spots.Spot
Exact key match.
OPX.Spots.Spot(spots, key)Returns
spot?
OPX.Spots.InBucket
Every spot in a bucket, sorted by key.
OPX.Spots.InBucket(spots, bucket)Returns
spot[]
OPX.Spots.FlatDistanceSquared
Squared horizontal distance (Z ignored).
OPX.Spots.FlatDistanceSquared(spot, x, y)Returns
number?
OPX.Spots.Nearest
Nearest spot within the squared radius; ties go to the lower key.
OPX.Spots.Nearest(spots, x, y, radiusSq)Returns
- spot:
spot? - distanceSq?:
number
OPX.Spots.ServerReach
The reach the server allows for a radius the client draws or an eye row offers: radius plus REACH_SLACK (clamped 0–4, 1.0 when unset). The server measures where it last saw the player, a moment behind the press.
OPX.Spots.ServerReach(radius)Parameters
- radius:
number— metres, e.g. the module'sUSE_RADIUS
Returns
number— metres
OPX.Spots.ServerReachSq
ServerReach(radius) squared, to compare with Nearest's distanceSq.
OPX.Spots.ServerReachSq(radius)Returns
number
local spot = OPX.Spots.Nearest(M.Spots, x, y, OPX.Spots.ServerReachSq(M.Config.USE_RADIUS))
if not spot then return OPX.Refuse(source, 'mymodule.tooFar', 'mymodule.use') endSpots on the client
lib/client/spots.lua, client only. The markers and the key every spot module (garages, dealership, clothing, teleports, the elevator door) used to copy. Build on it instead of writing your own marker loop.
OPX.Spots.Markers.New
A set of engine markers kept in line with a spot list.
local set = OPX.Spots.Markers.New(options)Parameters
- options:
table- tag:
string— your module id, for the one log line a refused marker writes - look:
function(spot): { shape, style, radius, lift }— usuallyOPX.Spots.Marker - maxDistance:
function(): number— metres; read once per pass - variant?:
function(spot): any— a marker whose variant changed is remade
- tag:
Returns
- set:
tableReconcile(list, x, y)— draws spots within range, removes the rest. Pass the position you already read (nil draws nothing). At mostOPX.Spots.MARKER_CREATES_PER_PASS(8) creates per call; removals are never deferred.Clear(),Count(),Has(key),Reset()
local markers = OPX.Spots.Markers.New({
tag = 'mymodule',
look = function() return OPX.Spots.Marker(M.Config.MARKER, DEFAULT_LOOK) end,
maxDistance = function() return OPX.Spots.MaxDistance(M.Config.MAX_DISTANCE, 60) end,
})
OPX.Scheduler.Every('mymodule.scan', 500, function()
local x, y = OPX.Spots.PlayerXY()
markers.Reconcile(M.Spots, x, y) -- 8 creates at most, so one pass stays cheap
end)OPX.Spots.Key.Register
Declares the rebindable key a spot module uses. A press while another surface holds the keyboard (chat, a form, the pause menu) does nothing; your handler runs under pcall.
OPX.Spots.Key.Register(options)Parameters
- options:
table- tag:
string - declared:
{ ID, NAME, DEFAULT }— your config'sKEYblock;DEFAULT = falsedeclares none - onPress:
function(origin)— called with'key'; decide yourself what a press away from your spots says (usually nothing) - wants?:
function(x, y, z): rank?, distance?— enters the mapping into the contest for its key. Answer nil when the press is nothing to you. Must not act. On a press it gets the position; fromShowsit gets nils and answers from your last scan.
- tag:
Returns
- registered:
boolean
local registered = OPX.Spots.Key.Register({
tag = 'mymodule',
declared = M.Config.KEY,
onPress = function() if near then use(near) end end,
wants = function(x, y)
if not near then return nil end
return OPX.Spots.Key.Rank('SPOT'), OPX.Spots.Key.Reach(near, x, y, nearSq)
end,
})OPX.Spots.Key.Label
The key the mapping is bound to now (the player's rebind, else DEFAULT), for a prompt row.
OPX.Spots.Key.Label(registered, declared)Returns
string?— nil when not registered
One press, one owner
The host fires every mapping bound to a key from one press. E is declared by garages, dealership, clothing, teleports, elevators and doorlock; X by progress, calls, hauling and animations. So a press is a contest: each contender answers wants with a rank from KEY_PRIORITY and a distance; the highest rank acts, then the nearer, then the lower id. When nobody wants the press, every handler runs as before. Only mappings bound to the same physical key contend: a player who rebinds one apart gets both back.
| Key | Order (default ranks) |
|---|---|
| E | a lockpick or Pick in world under way PICK 95 > the key closes a list it opened (garages, dealership) OPEN 90 > the spot you stand at SPOT 20. Two spots: the nearer wins, and only its strip row is drawn. |
| X | cancelable bar PROGRESS 100 > ringing call, decline RINGING 80 > outgoing call, withdraw OUTGOING 70 > carried crate CARRY 60 > own emote EMOTE 50 > call in progress, hang up CALL 10 |
| Y | the call's answer while a call rings; the hotbar peek contends but never claims, so it is silent while a call rings |
| Escape | the layer opened last (menu, form, panel, door panel, chat, call hologram); see Layer |
OPX.Spots.Key.Rank
The rank of a context name: KEY_PRIORITY[name], else OPX.Spots.Key.RANK_DEFAULTS[name], else 0.
OPX.Spots.Key.Rank(name)Parameters
- name:
'PROGRESS' | 'PICK' | 'OPEN' | 'RINGING' | 'OUTGOING' | 'CARRY' | 'EMOTE' | 'SPOT' | 'CALL'
Returns
number
OPX.Spots.Key.Contend
Enters a mapping into the contest for its key. Register does it for you when you pass wants; call it yourself for a mapping registered another way.
OPX.Spots.Key.Contend(options)Parameters
- options:
table- id:
string— the mapping id - default:
string— the declared default key - wants:
function(x, y, z): rank?, distance?— runs underpcall; must not act - tag?:
string
- id:
OPX.Spots.Key.Owns
Whether this press is this mapping's to act on. Ask it first in the handler. The first handler of a press decides for every contender on that key, before any has acted, and the decision is kept until each has asked, or for PRESS_MS (500 ms).
OPX.Spots.Key.Owns(id)Parameters
- id:
string
Returns
boolean—truefor a mapping that does not contend, and for everyone when nobody wants the press
OPX.Spots.Key.Contend({ id = 'mymodule.drop', default = 'X', wants = function()
if carrying then return OPX.Spots.Key.Rank('CARRY') end
end })
RegisterKeyMapping('mymodule.drop', 'Drop it', 'X', function()
if not OPX.Spots.Key.Owns('mymodule.drop') then return end
drop()
end)OPX.Spots.Key.Prime
Records the key a contender is bound to, for its strip row, so Shows makes no host read until a rebind. Register calls it with the host's answer.
OPX.Spots.Key.Prime(id, key)Parameters
- id:
string - key?:
string
OPX.Spots.Key.Shows
Files this mapping's claim from your scan and answers whether its key-strip row should be drawn: false when another mapping on the same key holds a claim that would take the press. Call it every scan, row or no row. A claim not renewed within CLAIM_MS (1.5 s) is ignored. A press still decides from live state.
OPX.Spots.Key.Shows(id)Parameters
- id:
string
Returns
boolean
OPX.Spots.Key.Reach
The flat distance from a position to a spot, for wants. With x = nil (a claim from Shows) it answers sqrt(measuredSq), the distance your scan already measured.
OPX.Spots.Key.Reach(spot, x, y, measuredSq)Parameters
- spot?:
{ x, y } - x?:
number - y?:
number - measuredSq?:
number
Returns
number?— nil ranks the spot behind any that can be measured
OPX.Spots.Key.Layer
Enters a screen layer into the Escape contest. The pseudo-key is OPX.Spots.Key.ESCAPE; the rank is when the layer opened, so the newest layer takes Escape and the rest stay open.
local id = OPX.Spots.Key.Layer(layer, openedAt)Parameters
- layer:
string— e.g.'menu' - openedAt:
function(): number?— ms when it opened, nil while shut
Returns
- id:
string—'escape.<layer>'
OPX.Spots.Key.TopLayer
Whether this Escape is this layer's. Ask it first in the pause handler.
OPX.Spots.Key.TopLayer(id)Returns
boolean
local openedAt
local escapeId = OPX.Spots.Key.Layer('mypanel', function() return openedAt end)
AddEventHandler('open77:pauseKey', function()
if not openedAt or not OPX.Spots.Key.TopLayer(escapeId) then return end
openedAt = nil
close()
end)OPX.Spots.Captured
Whether another surface holds the keyboard. A read that raises answers false.
OPX.Spots.Captured()Returns
boolean
OPX.Spots.PlayerXY
The player's ground position, or nil before there is a world.
local x, y = OPX.Spots.PlayerXY()Returns
- x?:
number - y?:
number
Vehicle
lib/shared/vehicle.lua, both sides.
OPX.Vehicle.DisplayName
The name a player reads for a vehicle model: a catalogue label as it is, or a TweakDB record made readable (namespace, v_, the class token such as standard2_ and _player dropped, words capitalised). Control characters are removed. The vehicle keys and the garage list both use it.
OPX.Vehicle.DisplayName(model, maxBytes)Parameters
- model:
string - maxBytes?:
integer— default 48
Returns
string?— nil for nothing usable
OPX.Vehicle.DisplayName('Vehicle.v_standard2_villefort_cortes_player') -- 'Villefort Cortes'OPX.Vehicle.IsAvRecord
Whether a TweakDB record starts with one of AV_PREFIXES, lower-cased. The one rule garages, dealership and admin share, and the only reader of AV_PREFIXES.
OPX.Vehicle.IsAvRecord(record)Parameters
- record:
string— TweakDB record name
Returns
boolean
OPX.Vehicle.AvLift
Metres above ground an AV is created at.
OPX.Vehicle.AvLift(value)Parameters
- value:
any— a module'sAV_LIFT
Returns
number—valueif 0–10, else1.2
OPX.Vehicle.AvLiftProblem
Appends a line when AV_LIFT is not 0–10.
OPX.Vehicle.AvLiftProblem(value, lines)Returns
string[]
Storage
lib/server/storage.lua, server only. The only door to the database (the MySQL bridge, which needs the database.access permission). Every call yields — call it from a thread, never at file scope — and answers a Result, never raising.
- Codes for the single-statement calls:
no-database(bridge not installed),query-failed(detailholds the raw database error; log it, never show it to a player). - Use named parameters (
@citizen), and never put a SQL comment inside a statement.
OPX.Storage.Query
OPX.Storage.Query(sql, params)Parameters
- sql:
string - params?:
table
Returns
Result— value: list of rows
local rows = OPX.Storage.Query('SELECT value FROM opx_thing WHERE citizen_id = @citizen', { citizen = citizenId })OPX.Storage.Single
OPX.Storage.Single(sql, params)Returns
Result— value: one row ornil(no row, not a failure)
OPX.Storage.Scalar
OPX.Storage.Scalar(sql, params)Returns
Result— value: one value
OPX.Storage.Insert
OPX.Storage.Insert(sql, params)Returns
Result— value: inserted id
OPX.Storage.Update
Also for DDL.
OPX.Storage.Update(sql, params)Returns
Result— value: rows affected
OPX.Storage.Execute
Same as Update.
OPX.Storage.Execute(sql, params)Returns
Result— value: rows affected
OPX.Storage.Transaction
All or nothing.
OPX.Storage.Transaction(statements)Parameters
- statements:
{ { query, values }, … }— each a SQL string and its parameters, as the bridge's transaction takes them (shipped callers use?placeholders withvalues)
Returns
Result— valuetrue; errorsno-database,transaction-raised,transaction-failed(rolled back)
local done = OPX.Storage.Transaction({
{ query = 'UPDATE a SET n = n - 1 WHERE id = ?', values = { 1 } },
{ query = 'UPDATE b SET n = n + 1 WHERE id = ?', values = { 2 } },
})OPX.Storage.Decode
Decodes a JSON column (string or already a table). A bad value answers fallback.
OPX.Storage.Decode(value, fallback)Returns
table | fallback
OPX.Storage.Nullable
JSON-encodes value, or '' for nil. Pair it with NULLIF(@x, '') in the SQL: the bridge drops nil parameters.
OPX.Storage.Nullable(value)Returns
string
OPX.Storage.Ready
Probes SELECT 1 once and remembers the answer for the life of the resource.
OPX.Storage.Ready()Returns
- ok:
boolean - reason:
string
OPX.Storage.ApplySchema
Runs each statement in order; stops at the first failure. Use OPX.Schema.Add rather than calling it.
OPX.Storage.ApplySchema(statements)Parameters
- statements:
string[]
Returns
Result— value: count; errorschema-failedwith the table name asdetail
OPX.Storage.EnsureIndex
Adds an index to a table that may predate it; a no-op when it is present. Asks information_schema.STATISTICS, then runs ALTER TABLE … ADD KEY. Safe on every boot. Usually reached through OPX.Schema.AddIndexes.
OPX.Storage.EnsureIndex(tableName, indexName, columns)Parameters
- tableName:
string - indexName:
string - columns:
string[]— at least one
Every identifier must match ^[%a_][%w_]*$.
Returns
Result— valuetrueadded,falsealready there; errorbad-index(bad identifier or empty column list,detail= index name), or a storage code
local ensured = OPX.Storage.EnsureIndex('opx_thing', 'idx_opx_thing_value', { 'value' })OPX.Storage.EnsureColumn
The column twin of EnsureIndex: adds a column to a table that may predate it; a no-op when it is present. Asks information_schema.COLUMNS, then runs ALTER TABLE … ADD COLUMN. Usually reached through OPX.Schema.AddColumns.
OPX.Storage.EnsureColumn(tableName, column, definition)Parameters
- tableName:
string— matches^[%a_][%w_]*$ - column:
string— matches^[%a_][%w_]*$ - definition:
string— type, size, character set,NULL/NOT NULL, a plain default; letters, digits,_, spaces,(,)and,only (no quotes, no;)
Returns
Result— valuetrueadded,falsealready there; errorbad-column(detail= column name), or a storage code
OPX.Storage.EnsureColumn('opx77_crafting_orders', 'token', 'VARCHAR(40) CHARACTER SET ascii COLLATE ascii_bin NULL')Carry
lib/server/storage.lua, server only. State that survives a resource reload, kept in the host's one-value-per-resource store (Open77.state) and split into namespaces so modules do not overwrite each other.
OPX.Carry.Load
What that namespace carried across the last reload.
OPX.Carry.Load(namespace)Parameters
- namespace:
string
Returns
any— ornil
OPX.Carry.Save
Stores a JSON-encodable value; nil clears that namespace only.
OPX.Carry.Save(namespace, value)Parameters
- namespace:
string - value:
any
Returns
- ok:
boolean - reason?:
string—bad-namespace,no-state-api, or the host's refusal
OPX.Carry.Save('thing', { round = M.round })
-- after the reload:
local carried = OPX.Carry.Load('thing')Audit
lib/server/audit.lua, server only. One greppable line per entry in the server log: [audit] event=… severity=… citizen=… user=… player=… message="…" data=…. Text is stripped of control characters and cut. Never raises.
OPX.Audit.Log
OPX.Audit.Log(entry)Parameters
-
entry:
table- event:
string - severity?:
'debug' | 'info' | 'warn' | 'error'— defaultinfo - message?:
string— cut to 200 characters - data?:
any— cut to 200 characters - source?:
integer - citizenId?:
string - userId?:
string
- event:
-
The same event from the same owner within 10 s is collapsed; the next line says
[+N suppressed]. -
info/debugevents startingmoney.orcharacter.are never collapsed.
OPX.Audit.Log({ event = 'thing.reset', severity = 'warn', source = source, message = 'reset by staff' })OPX.Audit.Player
Log with source, citizenId and userId taken from player.PlayerData.
OPX.Audit.Player(player, event, message, data)Parameters
- player:
table— a character player object - event:
string - message?:
string - data?:
any
OPX.Audit.Security
A warn entry for a refused or suspicious request. Pass source, or one player's spam hides everyone else's.
OPX.Audit.Security(event, message, data, source)Parameters
- event:
string - message?:
string - data?:
any - source?:
integer
OPX.Audit.Safe
Any value as log-safe text: control characters become spaces, cut to maximum characters with .... Use it on any text a client sent before you log it.
OPX.Audit.Safe(value, maximum)Parameters
- value:
any - maximum?:
integer— default 64
Returns
string
OPX.Audit.Forget
Drops a departing player's dedupe windows.
OPX.Audit.Forget(source, citizenId)Parameters
- source:
integer - citizenId?:
string
Life
lib/server/life.lua, server only. The one "is this player on the floor?" question every action asks before it serves a player (crafting, dealership, garages, shops, fitting rooms, vehicle keys, OpenStash). The down screen is drawn by the client, so closing it proves nothing.
OPX.Life.Down
Whether a player is down: the downed contract says so, or the host's isDead does (which still answers when downed is switched off). A read that raises counts as not down.
OPX.Life.Down(player)Parameters
- player:
integer— a server id;nil,0or less answersfalse
Returns
boolean
if OPX.Life.Down(source) then return OPX.Refuse(source, 'error.incapacitated', 'mymodule') endOPX.Lib
lib/client/lib.lua, client only. Loads opx_lib once with require('@opx_lib') and stores it as OPX.Lib. Raises at load if opx_lib is not running. Inside opx_infinity, use OPX.Lib.Input, OPX.Lib.Rpc and the rest instead of requiring it again. The server has no require, so OPX.Lib is nil there.
Surface
lib/client/surface.lua, client only. A thin wrapper over one WebUI page. The core builds the runtime's page with it; modules use OPX.UI instead. Channel names are given without the <id>: prefix.
OPX.Surface.Create
Creates a page and wires ready and diag.
OPX.Surface.Create(spec)Parameters
- spec:
table- id:
string - entry:
string - layer?:
string— defaulthud - zIndex?:
integer— default 700 - fps?:
integer— default 30 - width?, height?:
integer— default 1920×1080 - transparent?:
boolean— defaulttrue - visible?:
boolean— defaulttrue
- id:
Returns
- surface:
table? - why?:
string—bad_spec,no_webui, or the host's
OPX.Surface.On
Adds a handler (several may listen). nil clears the channel. Payloads that arrived before any handler (up to 8) are replayed to the first one. Handlers run under pcall.
OPX.Surface.On(surface, channel, handler)Parameters
- surface:
table - channel:
string - handler?:
function(payload)
Returns
function | boolean— a remover function
OPX.Surface.Send
false before ready or after Destroy. A refused payload is also sent once per channel to the server journal with OPX.Note.
OPX.Surface.Send(surface, channel, payload)Parameters
- surface:
table - channel:
string - payload?:
table
Returns
- sent:
boolean - refused:
boolean— the host rejected the payload
OPX.Surface.Wire
Registers the host listener for a channel before any handler exists, so early payloads are held, not lost.
OPX.Surface.Wire(surface, channel)Returns
boolean
OPX.Surface.Focus
Gives the page the keyboard and/or the cursor.
OPX.Surface.Focus(surface, keyboard, cursor)Parameters
- surface:
table - keyboard:
boolean - cursor:
boolean
Returns
boolean
OPX.Surface.Visible
Shows or hides the page without destroying it.
OPX.Surface.Visible(surface, visible)Parameters
- surface:
table - visible:
boolean
Returns
boolean
OPX.Surface.Destroy
Destroys the page; later Send and Focus answer false.
OPX.Surface.Destroy(surface)Events
The net events the core itself sends and listens to, the host event names in OPX.Host, the core's WebUI page channels, and its private internal events.
Error codes
The shared refusal vocabulary of opx_infinity — how a refusal reaches a player, the core error.* locale keys, and the Result convention with the codes the shared library answers.