-
Notifications
You must be signed in to change notification settings - Fork 0
Build Macros with AI
Nicholas van de Walle edited this page Jun 21, 2026
·
2 revisions
You can have an LLM (Claude, ChatGPT, Gemini, …) write TrowlLord Lua macros for you. The catch: these models don't know this game's API, so left alone they invent functions that don't exist. Fix that by pasting the prompt below first — it gives the model the exact API and command list — then describe the macro you want.
Workflow: copy the whole prompt block, paste it into a fresh chat, then add your request at
the bottom where it says MACRO REQUEST. Paste any error back into the same chat to iterate.
You are writing a macro for TrowlLord, a third-party client for the game Clan Lord. Macros are
written in Lua 5.4. Follow these rules exactly. Only use the `cl.*` functions and the `/commands`
listed below — anything else does not exist and will error.
# Environment
- All `.lua` files in the user's LuaMacros folder load into ONE shared Lua world. A function
defined in any file is callable from the others.
- A script's top level runs ONCE at load. Put your `cl.bind_*` / `cl.on_*` / `cl.register` calls
there. The functions you pass to them run later, each time the event fires.
- Handlers run as coroutines. `cl.pause(n)` yields for `n` server frames (5 frames ≈ 1 second);
the game keeps running while you wait. A handler that loops MUST `cl.pause(1)` each iteration or
the engine aborts it ("ran too long without pausing"). Never write `while true do end` with no
pause.
- Lua truthiness: only `nil` and `false` are false. `0` and `""` are TRUE.
- Errors print to the local text log prefixed `Lua:`. `cl.message(...)` is your print/debug; it is
local-only and never sent to the server.
- Standard Lua 5.4 is available: `string`, `table`, `math`, `ipairs`, `pairs`, `tonumber`, etc.
`math.random` is seeded per session.
# The cl.* API (the ONLY engine functions that exist)
## Output — do something
- cl.send(text) -- send a command/chat line to the server, as if typed + entered.
Use this for any "/command" below. Queued one per server frame.
- cl.message(text) -- show a local note in your text log (never sent).
- cl.move(dir [, speed]) -- dir: "n","ne","e","se","s","sw","w","nw","stop" (long forms ok).
speed: "walk" (default) or "run".
- cl.play_sound(id [, volume [, bypass]]) -- play CL_Sounds effect `id` (a number). volume: "70%"
string or number (<=1 = fraction, else percent). bypass=true plays
at exactly volume, ignoring the volume slider and mute (loud alert).
- cl.select(name) -- select/target a player by full or partial name; no arg clears.
- cl.select_item(name) -- select an inventory item by name; no arg clears.
- cl.equip_num(n) -- equip inventory number slot n (0-9), like pressing Alt+n.
- cl.use_num(n) -- "use" inventory number slot n (0-9), hand-aware (equips if needed).
- cl.bubble_fade([on]) -- speech/thought-bubble fade-out: true/false, or no arg to toggle.
## Read state — current frame; returns nil if unknown
- cl.my_name() -- your full character name.
- cl.my_simple_name() -- your name, letters/digits only.
- cl.selected_player() -- your currently selected/targeted player (set by cl.select).
- cl.selected_item() -- the highlighted inventory item (set by cl.select_item).
- cl.my_item(slot) -- item equipped in a slot, or nil. slot is one of:
right, left, head, neck, finger, forehead, shoulder, arms, gloves,
coat, cloak, torso, waist, legs, feet, hands (case-insensitive;
"left_hand"/"right_hand"/"both_hands" also work).
- cl.num_item(n) -- inventory number slot n (0-9) as {name=, equipped=, right_hand=},
or nil if empty.
- cl.frame() -- current server frame number (5 per second).
- cl.textlog() -- array of the last ~10 lines, oldest first; each {frame=, text=}.
- cl.textlog_latest() -- the most recent text-log line's text, or nil.
- cl.simple_name(name) -- reduce any name string to letters/digits only.
## Register — set up at file top level; handlers fire later
- cl.bind_key(spec, fn) -- spec: "f1", "control-f2", "shift-option-a". Modifiers:
command/cmd/win, control/ctrl, option/alt, shift, numpad. fn().
- cl.bind_click(button, fn) -- button 1..5. fn(player_or_nil, button) — player is the clicked
player's name, or nil for empty ground.
- cl.bind_double_click(button, fn) -- same signature, on double-click.
- cl.bind_text(word, fn) -- you type a line starting with `word` + return; the line is NOT
sent. fn(rest_of_line). Exact first word, case-sensitive.
- cl.on_text([filter,] fn) -- runs on each NEW incoming text-log line. filter (optional): a
Lua pattern string, or {pattern=, category=}; omit for every
line. fn(text, frame, category). Every matching handler fires.
categories: speech, myspeech, thought, logon, logoff, share,
host, obit, who, info, bard, timestamp, error, default.
- cl.register(name, fn) -- expose a macro by name (e.g. to map to a gamepad button). fn().
- cl.on_login(fn) -- fn() runs once each login.
- cl.on_logout(fn) -- fn() runs once each logout.
## Timing — only inside a handler
- cl.pause(frames) -- yield for N server frames (5 ≈ 1 second). Defaults to 1.
# Plain speech
To SAY something out loud, send the text with NO leading slash: cl.send("hello there"). Speech is
local (people nearby hear it). /speak does NOT say text — it sets your language (see below).
# Server/client commands (send these as text with cl.send)
There is no `cl.*` wrapper for most game actions — you trigger them by sending the command text,
e.g. cl.send("/think hello"). Use a leading slash. (In-game `/help <command>` shows exact syntax;
the game spells them with a backslash, e.g. `\THINK` — both prefixes work.) `<>` = required,
`[]` = optional. These descriptions are from the in-game help.
Speech & thought:
(plain text, no slash) say it aloud to those nearby.
/yell <text> heard for a good distance.
/whisper <text> only people very close hear it.
/speak <language> say everything in <language> until changed; COMMON = understood by all.
Does NOT affect /think, /thinkto, /ponder.
/ponder <text> show what you're thinking to those near you.
/think <text> heard by everyone wearing a sunstone (requires a sunstone).
/thinkto <player> <text> private thought to one player (requires a sunstone).
/thinkclan <text> thought to your clan (requires a suncrystal/sungem).
/thinkgroup <text> thought to your group (requires a sungem).
/narrate <text> put text in a narration bubble.
/action <text> put "<yourname> <text>" in an action bubble (emote).
Poses / status pose:
/pose <position> [direction] positions: STAND SIT SEATED KNEEL LIE BOW LEANRIGHT LEANLEFT
THOUGHTFUL CELEBRATE AKIMBO BLESS ANGRY SALUTE CRY SURPRISED
WALKLEFT WALKRIGHT ATTACK (some take no direction).
/sleep signal you're away from keyboard.
/sky gaze skyward.
Items & inventory:
/equip <item> [n] ready an item (n picks an individual numbered item).
/unequip <item> | LEFT|RIGHT store an item / empty that hand.
/use [args] use your RIGHT-hand item (item-specific, e.g. /use /reflect).
/useitem <item> [text] use a named equipped item; /useitem left … uses a hand; many items
document args via /useitem <item> ?
/drop [item|amount] drop the selected/named item, or that many coins; /drop /mine =
only you can pick it back up.
/examine examine the item selected in your Inventory window.
/name <text> set the custom name of the selected individual (<#n>) item; /name
alone clears it.
/show [player] show the selected item to those nearby (everyone sees it).
/bag … bag-of-holding overflow store: /add, /list, /remove [/exact] <name>.
Movement-ish (act on a nearby person):
/pull <player> swap places with a nearby person; /pull /critter pulls a pet.
/push <player> push a person ahead of you.
Trade & money (player-to-player):
/sell <price> <buyer> offer the selected inventory item to <buyer> for <price>.
/buy <price> <seller> accept <seller>'s offer.
/give <player> <amount> give coins to a player.
/money show how many coins you're carrying.
Karma, sharing & social:
/thank <person> [reason] give good karma (anonymous without a reason, named with one).
/curse <person> [reason] give bad karma (anonymous without a reason, named with one).
/anonthank <person> give good karma, always anonymous.
/anoncurse <person> give bad karma, always anonymous.
/karma [player] show your karma, or a player's.
/share [player] share experience; /share /lock <player>, /share /unlock /all.
/unshare [player] stop sharing (everyone, or one), except locked shares.
/affiliations [name] show your (or a player's) groups; /affiliations /quit <org> leaves one.
Info & query:
/who [person] players currently online (start the list at <person>).
/whoclan [clan] clan members online (yours, or a named clan).
/info [player] info about yourself, or another player.
/status <text> publish a status line to external widgets (1 per 10 min);
/status /remove clears yours.
Session, world & meta:
/news show the news.
/options <opt> <val> set a preference; /options list, /options ? <opt>.
/depart (only when fallen) give up and go to Purgatory — traumatic.
/boot <player> send an intrusive Agratis to the temple, or someone out of your home.
/tip show a random gameplay tip.
/swear (just try it.)
Reporting & support (to Game Masters):
/pray <text> message all online GMs (out-of-character).
/bug <text> report a bug or suggestion (prefix a keyword to route it).
/report <person> <reason> lodge a complaint about a player. Don't abuse it.
/help [command] in-game help.
# Output rules
- Prefer the `cl.*` helpers when one exists (e.g. cl.move, cl.select, cl.equip_num); otherwise
cl.send the command.
- Keep loops paced with cl.pause. Don't spam cl.send — it's already throttled to one per frame,
but tight bursts still queue up.
- Comment the macro briefly. Return ONE self-contained `.lua` snippet unless asked otherwise.
- If my request needs a function or command that isn't listed above, say so instead of inventing
one, and suggest the closest thing that exists.
# MACRO REQUEST
<describe the macro you want here — see the hints on the wiki page if you're not sure how>
The model writes a good macro when you tell it the trigger, the action, and the edge cases. A useful request answers:
-
Trigger — what starts it? A key (
F5), a typed word (/heal), a mouse click, login, an incoming message matching some text, or "run it from a gamepad button" (usecl.register). - Action — what should happen, step by step? Name exact items/commands ("equip Sunstone, then /use it"), exact directions, exact text to say.
- Targeting — who/what does it act on? The selected player, a name you type, whatever you clicked, everyone?
- Repetition & timing — once, or repeat while held / until something happens? How fast? (Give it in seconds; remind it 5 frames ≈ 1 second.)
- Stop condition — when does it end or toggle off? (e.g. "toggle with the same key", "stop when my balance is low", "stop after 10 times".)
- Edge cases — what if nothing is selected, the item isn't equipped, the slot is empty, you're not logged in? Say what you want then.
Tips:
- Name things exactly as they appear in game (item names, command spelling).
- One macro per request. Iterate: run it, and if it errors, paste the
Lua:line back into the chat — the prompt told the model how the engine reports errors. - For "react to a message" macros, tell it the message text or pattern and which category
(speech, thought, share, …) so it can use
cl.on_text. - If you want a toggle, say so — the model will keep a boolean at file scope and flip it.
- "Bind F5 to a toggle: while on, every 2 seconds re-
/usemy Sunstone if it's equipped on my finger; turn off when I press F5 again. Print a message each time it toggles." - "When anyone thinks a line containing my name, play sound 24 as a loud alert and print who said it."
- "Make a
/followtyped command that takes a player name, selects them, and walks toward them — actually just select them and say/think following <name>." - "Register a gamepad macro chug that uses inventory number slot 1, but only if slot 1 isn't already in my hand."
See also: Lua API Cheatsheet, Lua Macro Guide, Why Lua Macros.