Skip to content

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.