Helper Libraries¶
Pure-Lua convenience wrappers shipped under lua/lib/. These are not extra
bindings — they are ordinary Lua modules over the same eq2.* surface your own
scripts use. require them rather than re-deriving common patterns.
local inv = require("lib.inventory")
local nav = require("lib.nav")
Anything you drop in lua/ or lua/lib/ resolves the same way, so your own
modules sit alongside these.
lua/lib/¶
| Module | What |
|---|---|
lib.chat |
Named channel-id constants plus thin wrappers over eq2.on_chat / eq2.write_chat. |
lib.nav |
The /nav command set, moveto / follow / flank, and route save-load. See Navigation. |
lib.navpath |
Path solving over the Detour navmesh. See Navmesh pathfinding. |
lib.followbus |
Position beacon over the peer mesh, behind /nav followclose. See Close follow. |
lib.config |
Per-character config file load/save under config/. |
lib.inventory |
Item lookup and action helpers. See lib.inventory. |
lib.loot.loot |
Loot pipeline: corpse discovery, item extraction, dispatch. |
lib.loot.loottags |
Loot rule table: tag items keep/sell/broker/ignore, persist to config. Registers /autoloot. |
lib.loot.sell |
Vendor sell engine: drives the merchant Sell tab. Registers /sellall. |
lib.loot.broker |
Broker/market window automation. |
lib.loot.vendcontainer |
Broker consignment lot reader. |
lib.loot.notrade |
NO-TRADE confirmation handling. |
lib.loot.nbg |
Need-Before-Greed loot roller, rules-based, dry-run by default. Registers /nbg. |
lib.coin |
Coin parsing and formatting. |
lib.combo |
Heroic-opportunity / combo helpers. |
lib.abilitytypes |
Ability classification tables. |
lib.spellrank |
Spell rank/tier parsing. |
lib.dialog |
Conversation-advance policy over eq2.on_dialog / eq2.dialog.*, including reply_by_text. |
lib.quest |
Quest-grab state machine over eq2.on_quest_offer / eq2.quest.* / lib.dialog. |
lib.questjournal |
Quest journal tree walker: list, find, delete, share quests. |
lib.choice |
HUD.Choice popup interaction (rez confirms, fast-travel confirms, etc.). |
lib.travel |
Travel-select and retry state machine. |
lib.stats |
Self-only character stats (attributes, vitals, resists) plus actor-level DPS/Fervor. See lib.stats. |
lib.resident |
Resident script framework: command queue + worker loop for long-running scripts. |
lib.questui / lib.travelui |
ImGui panel bodies (.draw()) for lua/panels.lua. |
lib.discord.webhook |
Outbound Discord webhook client. |
lib.discord.format |
Discord message/embed formatting for loot announcements. |
Discord integration¶
lua/discord/ is a single resident script (/lua run discord) with two
independent directions, behind /discord
and /discordinbox:
| Module | What |
|---|---|
discord.init |
Entry point. Registers both commands, loads config, boots the other three modules, and pumps them in one tick loop. |
discord.loot |
Outbound. Watches your own and groupmates' loot chat and queues an embed for each match, sent through lib.discord.webhook. No relay bot needed — just a webhook URL. |
discord.inbox |
Inbound. Tails config/discord/inbox.jsonl, a file an external relay bot (outside the DLL) writes one authorized command per line to, and runs each through eq2.cmd. Disabled by default; every line must also match an allow_commands prefix. |
discord.config |
Shared config for both directions, one file at config/discord/discord.lua, written with defaults on first run. |
lua/gyonin/lib/¶
Used by the bundled combat automation. Note the gyonin.lib. prefix — these
are not under lua/lib/:
local combat = require("gyonin.lib.combat")
| Module | What |
|---|---|
gyonin.lib.targets |
Role-target resolution, PBAE and friendly actor filtering. |
gyonin.lib.casting |
Cast-and-wait helpers over Me.CastingSpell() / ability.IsReady(). |
gyonin.lib.combat |
Combat-state helpers. |
gyonin.lib.rotation |
Generic rotation-table execution — buff, cure, and assist passes. Drives the per-class profiles in lua/gyonin/classes/. |
gyonin.lib.gyonin_state |
Mode and persisted-settings state. |
Navigation¶
lib.nav wraps the raw movement bindings in the /nav
command set plus route persistence to config/routes/*.lua.
The bindings underneath, if you want to drive movement yourself:
eq2.waypoints.record(route, label) -- record current position into `route`
eq2.waypoints.clear(route)
eq2.waypoints.list(route) -- { {label, x, y, z, heading}, ... }
eq2.waypoints.start(route, loop) -- -> bool
eq2.waypoints.set_route(route, points) -- points = { {x=, y=, z=, heading=, label=}, ... }
eq2.waypoints.set_goal(x, y, z) -- retarget the ACTIVE route in place
eq2.waypoints.stop()
eq2.waypoints.status()
eq2.waypoints.tuning() / .set_tuning(t) -- steering parameters
status() returns:
{
hookReady, active, loop, index, count, distance, vertical,
headingError, forward, turnLeft, turnRight, route, state, reason,
}
Raw axis drives — rarely needed directly, since /nav fwd and /nav turn wrap
them with auto-release. Call every frame to hold; they are not latching:
eq2.move_drive(forward, strafe) -- each in [-1, 1]
eq2.move_release()
eq2.turn_drive(delta) -- heading delta per frame
eq2.turn_release()
Vertical and jump controls (event-driven, call once not per-frame):
eq2.jump(true) -- start jumping
eq2.jump(false) -- stop
eq2.move_up(true) -- swim/fly upward
eq2.move_down(true) -- swim/fly downward
Close follow¶
follow(name, dist) reads the leader out of your client's actor table — a
relayed, smoothed copy of someone else's character — and only resumes once the
distance has drifted past its resume band.
lib.followbus puts the leader's own client's position on the peer mesh
(channel nav.pos) so followclose(name, dist) can steer at a first-hand
position and, more to the point, resume on the moving flag in the report
instead of on drift. The follower is told the leader stepped off rather than
inferring it.
local followbus = require("lib.followbus")
followbus.pump() -- leader side, every tick (nav.tick does this)
followbus.subscribe() -- follower side; idempotent
followbus.sample(name, ttl_ms) -- { x, y, z, heading, zone, hp, moving, at } or nil
followbus.reporters(ttl_ms) -- who is broadcasting, freshest first
followbus.quiet() -- final "not moving" beacon on shutdown
Roughly 10 messages a second while moving and one a second while idle, throttled
by the module. nav.tick() pumps and subscribes, so any client running nav or
gyonin is already a beacon; navbeacon.lua covers a leader running neither.
Everything degrades rather than fails: with no mesh, no peers, or a leader
running nothing, sample() returns nil and the follow falls back to the actor
read. lib.nav announces the source in both directions and shows it in
/nav status as close(peer) or close(actor).
A name in a payload is a claim
The mesh is unauthenticated and reachable across your LAN, and the character
name a follower joins on travels inside the payload. Treat a report as a
claim about who sent it, not proof — which is why lib.nav cross-checks a
beacon against the leader's visible character when it can see one, and
refuses one that is implausibly far away.
Leash every movement path
A chase with no radius gate will drag you across the zone. lib.nav's camp
radius exists for this — an ungated line-of-sight chase once ran a character
into melee well outside its assist radius, and the symptom is only visible
with follow turned off.
Also remember Distance()
returns 0.0 on failure, so every distance gate needs d > 0.
Navmesh pathfinding¶
lib.navpath wraps the eq2.navmesh.* surface
into a follow-the-waypoints loop. The navmesh data comes from offline-baked zone
meshes under data/navmesh/.
local navpath = require("lib.navpath")
navpath.go(target_x, target_y, target_z)
| Function | What |
|---|---|
path_to(gx,gy,gz [,fx,fy,fz]) |
Plan a path to (gx,gy,gz). Start defaults to player position. Returns corners, nil or nil, reason. |
path_length(gx,gy,gz [,fx,fy,fz]) |
Total 3D path length to (gx,gy,gz). Start defaults to player position. Returns length or nil, reason. Runs the same Detour pipeline as path_to but returns a single number with no Lua table allocation — cheap enough to call per-mob for pull target sorting. |
load(key) |
Load a zone's navmesh by key. |
load_current() |
Load the navmesh for the current zone. |
loaded() |
Currently loaded zone key, or "". |
zones() |
List of zone keys with baked meshes. |
The /navpath command (from navpathtarget.lua or navpathfollow.lua) wraps
this into a script-level command.
Pathed routes¶
lib.nav uses pathed_route() internally for moveto, follow, flank, and
line-of-sight chase. When a navmesh is loaded for the current zone, it queries
lib.navpath.path_to() for a multi-corner A* path; when no mesh is loaded, it
falls back to a straight-line single-waypoint route.
Navmesh path corners carry a flags field from Detour. When the OFFMESH link
flag (0x04) is set on a corner, the waypoint is tagged with jump = true.
lib.nav can use this to echo a jump input at offmesh transitions (e.g. ledge
drops, short gaps). The route re-plans when the target moves more than 5 metres
from the last plan.
-- What a pathed route looks like:
-- { {x=1, y=2, z=3, heading=0},
-- {x=4, y=5, z=6, heading=0, jump=true}, -- offmesh link
-- {x=7, y=8, z=9, heading=0} }
See also /navmap for the top-down mesh
visualisation window.
lib.inventory¶
Wraps eq2.inventory() / eq2.equipped() with name lookup, and the native
command strings with named functions. Matching is case-insensitive and accepts
partial names.
| Function | What |
|---|---|
list() / list_equipped() |
Return eq2.inventory() / eq2.equipped(). |
find(name) / find_equipped(name) |
First matching item, or nil. |
actions(item) |
The action names the item's flag bits indicate. |
equip(item) / equip_by_name(name) |
inventory equip <index> -1 0 0 |
unequip(item) / unequip_by_name(name) |
inventory unequip <index> |
move(item, dest_bag) |
inventory move <index> <dest_bag> -3 0. dest_bag defaults to 0. |
examine(item) / examine_by_name(name) |
info inventory <index> |
scribe(item) / scribe_by_name(name) |
scribe_scroll_item <id> |
use(item) / use_by_name(name) |
use_item <index> |
read(item) / read_by_name(name) |
book read <index> |
local inv = require("lib.inventory")
local scroll = inv.find("pure awe")
if scroll then
eq2.print(table.concat(inv.actions(scroll), ", "))
inv.scribe(scroll)
end
lua/inv_helper.lua is a runnable front-end over the same library — see
/inv.
lib.stats¶
Character stats from the GameScene state blob (gs) and actor struct. The gs
block is self-only — these functions read data that only exists for the local
player. DPS and Fervor are actor-struct fields and accept any actor.
local stats = require("lib.stats")
eq2.print(("STR %d STA %d AGI %d WIS %d INT %d")
:format(stats.Strength(), stats.Stamina(), stats.Agility(),
stats.Wisdom(), stats.Intelligence()))
eq2.print(("HP %d/%d Power %d/%d")
:format(stats.CurrHP(), stats.MaxHP(), stats.CurrPower(), stats.MaxPower()))
eq2.print(("DPS %.1f Fervor %.1f%%")
:format(stats.DPS(mq.TLO.Me), stats.Fervor(mq.TLO.Me)))
Self-only (GameScene)¶
| Function | Returns |
|---|---|
CurrHP() / MaxHP() |
Raw HP (integer, not percent) |
CurrPower() / MaxPower() |
Raw power |
HPRegen() / PowerRegen() |
Regen rate |
Strength() |
Strength |
Stamina() |
Stamina |
Agility() |
Agility |
Wisdom() |
Wisdom |
Intelligence() |
Intelligence |
ElementalResist() |
Elemental resist |
NoxiousResist() |
Noxious resist |
ArcaneResist() |
Arcane resist |
Any actor¶
| Function | Returns |
|---|---|
DPS(actor) |
DPS (float, e.g. 38.8) |
Fervor(actor) |
Fervor % (float, e.g. 37.1) |
dump(actor?)¶
Prints a formatted summary of all stats to the console. Defaults to mq.TLO.Me.
Editing a shared module¶
/lua restart with no name reloads every running script, which is what you want
after editing anything under lua/lib/ — several scripts will have required
it, and only a full restart picks the change up everywhere.
/lua restart
See also¶
- Events and Binds — the bindings these wrap.
- Slash Commands