Differences from MacroQuest¶
clockwork's architecture is deliberately modelled on MacroQuest, so most of what you know transfers. What follows is what does not.
No ${...} parser¶
MacroQuest's defining feature is the string parser: ${Me.PctHP} evaluated
inside any command line. clockwork has no parser. TLOs are bound directly
into Lua as real function calls:
-- MacroQuest
/echo ${Me.PctHP}
-- clockwork
eq2.print(mq.TLO.Me.PctHP())
Every accessor is a call, including the leaf: mq.TLO.Me.Level(), not
mq.TLO.Me.Level. Values are read live on every call, never cached.
This also means there is no /echo, no /varset, no macro data types in the
MacroQuest sense — Lua's own variables and string.format cover it.
No macros¶
There is no .mac language and no /macro command. Lua is the only scripting
surface.
No plugins¶
There is no C-ABI plugin interface, and adding one is an explicit non-goal. What
plugins were for — reacting to game events, adding commands, drawing UI — is
covered by the Lua event substrate (eq2.on_* / eq2.off_*),
eq2.register_command(), and eq2.imgui.init().
mq is an alias for eq2¶
The Lua global table is eq2. mq is bound to the same table, so
mq.TLO.Me.Name() and eq2.TLO.Me.Name() are identical. Use whichever reads
better to you; the bundled scripts use mq.TLO.* for state reads and eq2.*
for actions, purely as a convention.
Cooperative scripts, not preemptive¶
Each script runs as its own LuaJIT coroutine, resumed once per game pulse. You
must yield periodically with eq2.delay(ms). A single resume that runs
longer than 500 ms without yielding is killed by a watchdog on the assumption
it's an infinite loop. Write loops, not busy-waits:
while true do
do_one_pass()
eq2.delay(250) -- required
end
Multibox is built in¶
There is no EQBC or DanNet equivalent to install. Every injected client
registers in a shared table and /dga / /dge broadcast across it. The Lua
side is eq2.peer.*.
/dga maps to MacroQuest's "all including me", /dge to "everyone but me".
No XTarget¶
EverQuest II has no extended-target window, so there is no ${Me.XTarget}
equivalent. Combat-target lists have to be built from mq.TLO.Spawns() plus
InCombat() / HasAggro() filtering. The bundled lib.targets does this.
Fail-soft everywhere¶
Every TLO accessor returns a safe zero value on failure — 0, "", false, or
nil — and never raises a Lua error, even out-of-world or when a signature
failed to resolve. Guard on the value you care about:
local t = mq.TLO.Target
if t.ID() ~= 0 then -- correct
...
end
not on whether the call "worked". A notable consequence: Distance() returns
0.0 when it can't resolve a position, so if d > radius reads a failed
lookup as "on top of me". Movement gates should require d > 0.