OPX//77
opx_infinityCore

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 be nil

Returns

  • { ok = true, value }

OPX.Result.Err

OPX.Result.Err(code, detail)

Parameters

  • code: string
  • detail?: string

Returns

  • { ok = false, error, detail }
Example
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
Example
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? — nil if 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
Example
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
Example
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
Example
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? — nil for 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? — nil when nothing is left
Example
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 answers nil, '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

Returns

  • Result — value is the trimmed text; errors type, too-long, not-utf8, too-short, format
Example
local name = OPX.Validate.Text(payload.name, { min = 2, max = 32 })
if not name.ok then return OPX.Refuse(source, 'error.badRequest', 'thing.rename') end

OPX.Validate.Number

OPX.Validate.Number(value, opts)

Parameters

  • value: any
  • opts?: table
    • integer?: boolean
    • min?: number
    • max?: number

Returns

  • Result — value is the number; errors type, 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 exactly false to veto
  • priority?: number

Returns

  • integer — id
Example
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; true when no hook is registered

Hook points

Raised by the shipped modules (server side, all in character). Payload fields and refusal codes: character.

NamePayloadA 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 — false for 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
Example
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>
Example
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; default SHARED.MONEY.DEFAULT, else 'EDDIES'

Returns

  • string
Example
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) — default math.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; errors type, length (more than 32 bytes, or not 7 symbols), alphabet, checksum
Example
local id = OPX.CitizenId.Parse('k7m 4 dxw')
if id.ok then print(id.value) end -- 'K7M-4DXW' if the check symbol matches

Anchors

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 0 means "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 Problems reports 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 read

OPX.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)

Returns

  • allowed: boolean
  • code?: string — nearest miss first: off_duty, grade_too_low, job_required; also no_character and job_stale (snapshot older than maxAgeMs, from the future, or a clock that cannot be read)
Example
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) end

OPX.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

config
SPOTS = {
  watson_kabuki = { KIND = 'garage', LABEL = 'Kabuki', X = -1442.2, Y = 127.4, Z = 18.0,
                    HEADING = 90.0, BUCKET = 0 },
}
FieldRule
keyString, 1 to spec.maxKey characters.
KINDRequired only when the module has kinds; one of spec.kinds, case-insensitive.
LABELOptional string; defaults to the key.
X, Y, ZFinite numbers within ±1 000 000 (OPX.Spots.BOUND).
HEADINGOnly for modules whose spec has heading; finite number, default 0.0.
BUCKETWhole 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? }. noun is the word used in refusals ('spot', 'dealer', 'store').

The marker format

config
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
FieldAllowedBad value
shapering, cylinderthe module's default shape
styleinteraction, objective, spawn, dangerthe module's default style
RADIUS0.1–50the module's default radius
MAX_DISTANCE1–500 mthe module's default
GROUND_OFFSET0–2 m0.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>
Example
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) end

OPX.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's USE_RADIUS

Returns

  • number — metres

OPX.Spots.ServerReachSq

ServerReach(radius) squared, to compare with Nearest's distanceSq.

OPX.Spots.ServerReachSq(radius)

Returns

  • number
Example — server
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') end

Spots 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 } — usually OPX.Spots.Marker
    • maxDistance: function(): number — metres; read once per pass
    • variant?: function(spot): any — a marker whose variant changed is remade

Returns

  • set: table
    • Reconcile(list, x, y) — draws spots within range, removes the rest. Pass the position you already read (nil draws nothing). At most OPX.Spots.MARKER_CREATES_PER_PASS (8) creates per call; removals are never deferred.
    • Clear(), Count(), Has(key), Reset()
Example — a scheduler job
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's KEY block; DEFAULT = false declares 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; from Shows it gets nils and answers from your last scan.

Returns

  • registered: boolean
Example
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.

KeyOrder (default ranks)
Ea 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.
Xcancelable 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
Ythe call's answer while a call rings; the hotbar peek contends but never claims, so it is silent while a call rings
Escapethe 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 under pcall; must not act
    • tag?: string

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 — true for a mapping that does not contend, and for everyone when nobody wants the press
Example
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
Example
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
Example
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's AV_LIFT

Returns

  • number — value if 0–10, else 1.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 (detail holds 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
Example
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 or nil (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 with values)

Returns

  • Result — value true; errors no-database, transaction-raised, transaction-failed (rolled back)
Example
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; error schema-failed with the table name as detail

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 — value true added, false already there; error bad-index (bad identifier or empty column list, detail = index name), or a storage code
Example
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 — value true added, false already there; error bad-column (detail = column name), or a storage code
Example
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 — or nil

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
Example
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' — default info
    • message?: string — cut to 200 characters
    • data?: any — cut to 200 characters
    • source?: integer
    • citizenId?: string
    • userId?: string
  • The same event from the same owner within 10 s is collapsed; the next line says [+N suppressed].

  • info/debug events starting money. or character. are never collapsed.

Example
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, 0 or less answers false

Returns

  • boolean
Example
if OPX.Life.Down(source) then return OPX.Refuse(source, 'error.incapacitated', 'mymodule') end

OPX.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 — default hud
    • zIndex?: integer — default 700
    • fps?: integer — default 30
    • width?, height?: integer — default 1920×1080
    • transparent?: boolean — default true
    • visible?: boolean — default true

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)

On this page

ResultOPX.Result.OkOPX.Result.ErrOPX.Result.KindTableOPX.Table.DeepCopyOPX.Table.CountStringOPX.String.LengthOPX.String.TrimOPX.String.InterpolateOPX.String.RandomMathOPX.Math.ClampOPX.Math.IsFiniteOPX.Math.FiniteOPX.Math.DistanceSquaredOPX.Math.GroupDigitsTextOPX.Text.SpanOPX.Text.CleanOPX.Text.BytesOPX.Text.FiniteOPX.Text.IntegerOPX.Text.RestOPX.Text.SwitchOPX.Text.SlugValidateOPX.Validate.TextOPX.Validate.NumberHooksOPX.Hooks.RegisterOPX.Hooks.RemoveOPX.Hooks.TriggerHook pointsLocaleOPX.Locale.RegisterOPX.Locale.SetOPX.Locale.CurrentOPX.Locale.ExistsOPX.Locale.TextOPX.Locale.CatalogueOPX.Locale.CatalogueSlicedOPX.Locale.MoneyCitizenIdOPX.CitizenId.BuildOPX.CitizenId.GenerateOPX.CitizenId.ParseAnchorsOPX.Anchors.ResolveJobGateThe JOBS formatOPX.JobGate.EvaluateOPX.JobGate.ProblemsSpotsThe spot formatThe marker formatOPX.Spots.CoordinateOPX.Spots.IntegerOPX.Spots.MarkerOPX.Spots.MaxDistanceOPX.Spots.MarkerProblemsOPX.Spots.DrawProblemsOPX.Spots.FromDefinitionOPX.Spots.FromWireOPX.Spots.SerialiseOPX.Spots.CoerceOPX.Spots.SpotOPX.Spots.InBucketOPX.Spots.FlatDistanceSquaredOPX.Spots.NearestOPX.Spots.ServerReachOPX.Spots.ServerReachSqSpots on the clientOPX.Spots.Markers.NewOPX.Spots.Key.RegisterOPX.Spots.Key.LabelOne press, one ownerOPX.Spots.Key.RankOPX.Spots.Key.ContendOPX.Spots.Key.OwnsOPX.Spots.Key.PrimeOPX.Spots.Key.ShowsOPX.Spots.Key.ReachOPX.Spots.Key.LayerOPX.Spots.Key.TopLayerOPX.Spots.CapturedOPX.Spots.PlayerXYVehicleOPX.Vehicle.DisplayNameOPX.Vehicle.IsAvRecordOPX.Vehicle.AvLiftOPX.Vehicle.AvLiftProblemStorageOPX.Storage.QueryOPX.Storage.SingleOPX.Storage.ScalarOPX.Storage.InsertOPX.Storage.UpdateOPX.Storage.ExecuteOPX.Storage.TransactionOPX.Storage.DecodeOPX.Storage.NullableOPX.Storage.ReadyOPX.Storage.ApplySchemaOPX.Storage.EnsureIndexOPX.Storage.EnsureColumnCarryOPX.Carry.LoadOPX.Carry.SaveAuditOPX.Audit.LogOPX.Audit.PlayerOPX.Audit.SecurityOPX.Audit.SafeOPX.Audit.ForgetLifeOPX.Life.DownOPX.LibSurfaceOPX.Surface.CreateOPX.Surface.OnOPX.Surface.SendOPX.Surface.WireOPX.Surface.FocusOPX.Surface.VisibleOPX.Surface.Destroy