Lua Scripting¶
Lua is the only scripting surface in clockwork. There is no macro language and no plugin ABI — automation, UI, and event handling are all Lua.
The runtime is LuaJIT, so the language is Lua 5.1 plus LuaJIT's extensions.
The model¶
Each script runs as its own coroutine, resumed once per game pulse. That makes scripts cooperative: your script holds the game thread while it runs, and gives it back when it yields.
while true do
do_one_pass()
eq2.delay(250) -- yields; resumes in 250ms
end
eq2.delay(ms) is the yield. You must call it — a single resume that runs
longer than 500 ms without yielding is killed by a watchdog on the
assumption it's an infinite loop.
Because everything runs on the game thread, you never need locks, and you never have to ask whether it's safe to touch game state from where you are. The tradeoff is that a slow pass costs frames.
The eq2 table¶
Everything clockwork exposes hangs off one global table, eq2. mq is an alias
for the same table.
| Area | Where |
|---|---|
| Reading game state | mq.TLO.* — see Top-Level Objects |
| Output | eq2.print, eq2.write_chat, eq2.chat_timestamps |
| Running commands | eq2.cmd, eq2.cmdf |
| Timing | eq2.delay, eq2.gametime |
| Commands and events | eq2.register_command, eq2.on_* / eq2.off_* |
| Movement | eq2.waypoints.*, eq2.move_drive, eq2.turn_drive, eq2.jump, eq2.move_up, eq2.move_down |
| Inventory | eq2.inventory, eq2.equipped, eq2.inventory_space |
| Loot and commerce | eq2.loot.*, eq2.broker.* |
| Multibox | eq2.peer.* |
| Native UI | eq2.ui.* |
| Quest / dialog / travel | eq2.quest.*, eq2.dialog.*, eq2.travel.*, eq2.zone_door.*, eq2.quest_object.*, eq2.reply_dialog.* |
| Navmesh | eq2.navmesh.* |
| Network observation | eq2.on_net_msg, eq2.net_stats |
| ImGui | eq2.imgui.init, plus the global ImGui table |
See Events and Binds for the action and event surface,
and Helper Libraries for the pure-Lua wrappers shipped
under lua/lib/.
Reading vs acting¶
The two halves behave differently and it's worth internalising early:
Reads (mq.TLO.*) are live, cheap, and fail soft. Call them whenever you
want; they never error and never block.
Actions (eq2.cmd, ability use, movement) go through the game's own command
path and take effect on some later frame. They are not synchronous. Code that
acts and immediately reads back its own effect will usually read the old value:
eq2.cmdf("/target_id %d", id)
eq2.delay(100) -- give the client a pulse
if mq.TLO.Target.ID() == id then ... end
Your first script¶
lua/hello.lua:
for i = 1, 5 do
eq2.print("tick " .. i)
eq2.cmd("/gsay clockwork lua tick " .. i)
eq2.delay(1000)
end
eq2.print("done")
/lua run hello
That one exits on its own after 5 ticks — nothing to stop. For a script that stays running until you stop it, see the loop pattern in Getting Started.
Read on: Getting Started.
Where scripts live¶
| Path | For |
|---|---|
lua/<name>.lua |
A single-file script. |
lua/<name>/init.lua |
A multi-file script. |
The flat file wins if both exist. require("lib.nav") and friends resolve
against lua/lib/, and your own modules dropped in lua/ or lua/lib/ resolve
the same way.
Cleanup is automatic¶
When a script stops, clockwork sweeps everything it registered: commands, event tokens, ImGui windows. You do not need to unregister anything on the normal shutdown path.
The corollary is that a script which registers a command and then returns
immediately loses that command. Scripts that provide commands sit in a
while true loop for exactly this reason.