Globals
The Lua globals are a small set of functions injected into every Lua script’s global scope by the engine. They provide the bridge between MD scripts and Lua UI code — sending events, registering handlers, calling Lua from MD.
If you’re writing a mod with a custom UI menu, these are the functions that let MD signal your menu.
Core globals
Section titled “Core globals”MD → Lua bridge
Section titled “MD → Lua bridge”| Function | Purpose |
|---|---|
RegisterEvent(eventname, handler) | Subscribe to an MD event |
UnregisterEvent(eventname, handler) | Unsubscribe |
RemoveAllUITriggeredEvent() | Cleanup (call in menu close) |
Lua → MD bridge
Section titled “Lua → MD bridge”| Function | Purpose |
|---|---|
AddUITriggeredEvent(name, value) | Fire an MD event from Lua |
Script registration
Section titled “Script registration”| Function | Purpose |
|---|---|
SetScript(eventname, handler) | Register UI event handler |
CallEventScripts(eventname, args...) | Invoke registered scripts |
CallUpdateScripts(dt) | Per-frame update tick |
| Function | Purpose |
|---|---|
Helper | Global table — see Helper API |
ffi | Global table — see FFI |
print(...) | Write to debug log |
Common patterns
Section titled “Common patterns””Register an MD event listener”
Section titled “”Register an MD event listener””function init() RegisterEvent("MyMod.UpdateState", onUpdateState)end
function onUpdateState(_, value) -- value is the MD signal's param print("Got state update:", value)endIn MD, fire the event:
<raise_lua_event name="'MyMod.UpdateState'" param="'new state'"/>The MD action <raise_lua_event> is the canonical MD-side trigger. Lua’s RegisterEvent is the canonical receive side.
”Fire an event from Lua to MD”
Section titled “”Fire an event from Lua to MD””AddUITriggeredEvent("MyMod_MenuClosed", "ok")In MD:
<cue name="WatchClose" instantiate="true"> <conditions> <event_ui_triggered screen="'MyMod_MenuClosed'" control="'ok'"/> </conditions> <actions> <!-- handle Lua-side menu close --> </actions></cue>“Per-frame update”
Section titled ““Per-frame update””function onUpdate(dt) -- dt is delta seconds since last frame accumulator = (accumulator or 0) + dt if accumulator > 1.0 then -- tick once per second updateThing() accumulator = 0 endend
-- registerSetScript("onUpdate", onUpdate)Common gotchas
Section titled “Common gotchas”- ⚠
RegisterEventcallbacks are global. Re-registering replaces the previous handler. To support multiple handlers for the same event, use a dispatcher pattern. - ⚠ Callbacks fire AFTER MD action completes. Don’t expect synchronous response from
<raise_lua_event>. - ⚠
AddUITriggeredEventand<event_ui_triggered>use different parameter names. Lua side: name + value. MD side:screen=(matches name) +control=(matches value). Mismatch silently drops events. - ⚠ Event names are STRINGS. Don’t use enums or constants — Lua-side and MD-side must use literally the same string. Convention: prefix with mod name.
- ⚠
RemoveAllUITriggeredEventshould be called in menu cleanup. Otherwise event handlers leak across menu lifecycles. - ⚠
print()output goes todebug.log. Use it for debug logging; do not rely on a console. Vanilla also usesDebugError,Helper.debugText. - ⚠
SetScriptis for menu scripts, not arbitrary handlers. SpecificallyonUpdate,onShowMenu,cleanupand similar named lifecycle slots.
Examples
Section titled “Examples”Example 1: Bi-directional MD ↔ Lua
Section titled “Example 1: Bi-directional MD ↔ Lua”-- Lua sidefunction init() RegisterEvent("MyMod.DataPush", onDataReceived)end
function onDataReceived(_, payload) -- handle MD data print("Got:", payload)
-- respond back AddUITriggeredEvent("MyMod_DataReceived", "ack")end<!-- MD side --><cue name="SendData" instantiate="true"> <conditions>...</conditions> <actions> <raise_lua_event name="'MyMod.DataPush'" param="$myData"/> </actions></cue>
<cue name="OnAck" instantiate="true"> <conditions> <event_ui_triggered screen="'MyMod_DataReceived'" control="'ack'"/> </conditions> <actions> <write_to_logbook text="'Lua received'"/> </actions></cue>Example 2: Per-frame UI update
Section titled “Example 2: Per-frame UI update”local function onUpdate(dt) if menu and menu.frame then menu.frame:setText("Time: " .. tostring(GetCurTime())) endend
SetScript("onUpdate", onUpdate)Example 3: Cleanup pattern
Section titled “Example 3: Cleanup pattern”function cleanup() RemoveAllUITriggeredEvent() menu.frame = nil menu.infoFrame = nil -- IMPORTANT: prevent invalid fontstring spamendArchitectural context
Section titled “Architectural context”- MD-Lua bridge is one-way per call.
<raise_lua_event>triggers Lua handlers (with param);AddUITriggeredEventtriggers MDevent_ui_triggeredcues. Round-trip requires both. - String-based wiring. Engine doesn’t validate event-name strings — typos silently fail. Convention: prefix with mod identifier (
mlog_X). - Performance:
RegisterEventcallbacks fire on every matching event. Filter inside the callback for high-frequency events.
Related
Section titled “Related”- Helper API — UI construction.
- FFI — C function calls.
- SN APIs — wrapper APIs.
- MD Action —
<raise_lua_event>action. - MD Condition —
event_ui_triggeredevent.