Skip to content

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.