lua - ryzom/ryzomcore GitHub Wiki


title: Lua Scripting description: The interface Lua API, widget reflection, and building whole applications in interface data published: true editor: markdown

Lua is the scripting layer of the interface: scripts see every widget as a live object, can rewire anything at runtime, and can carry entire applications. The showcase's Minesweeper, drag-and-drop inventory and the console below are pure XML+Lua, with no game code in the binary.

The Lua console demo: evaluate an expression, inspect a group with ls(), read a live widget property

Loading scripts

<lua file="minesweeper.lua" />

<lua> elements execute at parse time, in order, in one shared Lua state. A script error is fatal to interface loading (everything else in the parser merely warns; scripts are assumed load-bearing). Functions defined in one file are visible to all later files and to every handler.

Scripts hook into the UI through the lua action handler: onclick_l="lua" params_l="game.onClick(1)", onenter="lua" params="console.enter()", and the engine itself runs setOnDraw scripts and lua_class associations through the same handler.

The API

What the library registers (available in any embedding that calls initLUA):

UI manipulation: setOnDraw(group, "script") (per-frame tick; the Minesweeper timer and the inventory gesture loop live here), getOnDraw, addOnDbChange(group, "@UI:A,@UI:B", "script") / removeOnDbChange (react to database changes), runAH(ctrlOrNil, "ah", "params"), runExpr("expr") / runFct("fct", ...) (interface expressions), getUIId(elt), deleteUI(elt), setCaptureKeyboard(ctrl) / resetCaptureKeyboard, setTopWindow(win), disableModalWindow, getCurrentWindowUnder(), getWindowSize(), getTextureSize("name").

Environment: runCommand (NeL console commands), fileExists, fileLookup, getPathContent (a filesystem listing, nothing to do with UI children), nltime.getPreciseLocalTime() (seconds, high resolution), nltime.getLocalTime() (ms), nltime.getSecondsSince1970(), nlfile.*, i18n.get("uiKey"), concatString, findReplaceAll, and the CRGBA/CVector2f classes.

From the embedder: getUI("ui:path") is not part of the library. Every application supplies its own widget lookup (the client's does extra work; the sample registers a minimal one). The client additionally provides getUICaller(), getDbProp/setDbProp, formatUI, dumpUI, messageBox, createGroupInstance and a large game-side surface, so check what your host registers. The sample adds getMousePos()/getMouseDown() for gesture work (below).

Lua 5.1 semantics apply: math.fmod not % on floats in old idioms, #t for length, loadstring for eval.

Widgets as objects: reflection

getUI returns a userdata wrapping the element. Three things work on it:

Properties: every reflected property reads and writes as a field: w.active = false, txt.hardtext = 'Hello', bmp.color = '150 255 150 255', eb.input_string, plus the read-only computed geometry x_real/y_real/w_real/h_real (see Layout). Wrong property names read as nil (safe to probe) but assigning an unknown property raises an error.

Child navigation: child elements are fields: win.content.buttons.ok, cell.cover.active = false. elt.parent walks up. elt.isNil is true if the underlying C++ object has been destroyed (the wrapper is a safe reference, not ownership).

Enumeration: widget userdata implement the __next metamethod. Redefine next/pairs to honor it (the Ring scripts do exactly this) and pairs(widget) iterates children first (groups, then views, then ctrls; keys stringify like "_Group 3", values are the child widgets), then every reflected property (keys stringify to the property names):

local oldNext = next
function next(t, k)
	local mt = getmetatable(t)
	if mt and mt.__next then return mt.__next(t, k) end
	return oldNext(t, k)
end
function pairs(t) return next, t, nil end

for k, v in pairs(getUI('ui:sample:views')) do
	print(tostring(k), tostring(v))
end

This is the foundation of the console's ls() (children by kind) and props() (property dump) helpers: a complete UI inspector in ~40 lines of Lua.

Patterns from the demo apps

Reflection-driven game state: Minesweeper

Minesweeper mid-game: 81 template-stamped cells driven entirely from Lua

The board is a <vector> of 81 cell templates; all rules live in Lua. A cell reveal is just cell.cover.active = false; flags toggle a marker bitmap; the timer is a setOnDraw script comparing nltime.getPreciseLocalTime(). Buttons pass their index through their params: onclick_l="lua" params_l="mines.reveal($i)" stamped by the vector.

A REPL over the live tree: the console

The console log is a vector of text views repainted from a history table; the input is a stock edit box with onenter="lua" params="console.enter()" and enter_loose_focus="false" (the trap). Evaluation tries loadstring('return '..line) first so expressions print their value, then falls back to statement form; print is redirected into the log. Because it runs in the same state as every other script, it can poke any running demo live, which is indispensable for debugging wiring.

Drag and drop: the inventory pattern

Mid-drag: the ghost icon follows the cursor and the valid target slot glows After the drop: the helmet sits in its typed slot

The library has no drag widget; the inventory does gestures in data using two embedder hooks and a per-frame tick:

-- inside setOnDraw of the inventory window
local mx, my = getMousePos()
local down = getMouseDown()
-- press edge: hit-test slots against x_real/y_real/w_real/h_real
-- while dragging: ghost.x = mx - content.x_real - 20 (BL-anchored ghost bitmap)
-- release edge: accept/swap/reject by comparing slot kind tags in Lua

Slots are plain groups; the dragged item is a render_layer-raised ghost bitmap; the glow is an active toggle on a highlight bitmap; acceptance rules are table lookups. Coordinates from the mouse hooks are interface pixels, the same space as the reflected *_real geometry, so hit-testing is a four-comparison function. The hooks themselves (getMousePos, getMouseDown, and the pointer button mirroring they depend on) are part of the embedding contract.

Debugging tips

  • Ship a console (the sample's is ~150 lines of XML+Lua, reusable); live inspection beats log archaeology.
  • runAH from the console exercises any wiring exactly as a click would.
  • Parse-time scripts can call getUI on elements declared earlier in the parse; defer anything touching later elements to a first setOnDraw tick or an on_active handler.
  • Errors inside AH-invoked scripts land in the application log, not on screen; keep it visible while iterating.
⚠️ **GitHub.com Fallback** ⚠️