Scripting¶
Telix scripts are Python files that run asynchronously using the asyncio interface. They are useful for logic that goes beyond the “find pattern and respond” capability of Trigger Editor: hunting loops, healing routines, mapping runs, or any sequence that depends on timing or server output.
Quickstart¶
Create a file named demo.py in the current directory where telix is
launched or in ~/.config/telix/scripts/:
from telix.scripts import ScriptContext
async def run(ctx: ScriptContext) -> None:
room = ctx.room
ctx.print(f"[demo] You are in: {room.name} ({room.area})")
ctx.print(f"[demo] Exits: {', '.join(room.exits)}")
Then in the Telix REPL, run it using backtick command:
`async demo`
Example output:
[demo] You are in: Mayor's office (caladan)
[demo] Exits: west
Starting¶
Telix looks for scripts in this order:
Current working directory
~/.config/telix/scripts/
Scripts are started by the `async` or `await` commands. Every time a script is
launched, the last-modified time is checked and reloaded if necessary. Save a script file at any
time and then call it to see any new changes (or errors!).
`await NAME`Start a script and block until it finishes before continuing. This is preferred for most trigger, macro, or autowalk/autodiscover integration, as you would want the script to run to completion before continuing:
`await combat.hunt`
`async NAME`Start a script in the background and return immediately. The script runs concurrently with everything else:
`async combat.hunt`
This loads
combat.pyand callshunt(ctx)asynchronously.
With either command calls, the function name can be omitted, and is then assumed as run.
`async demo`loadsdemo.py, callsrun(ctx).
Scripts can accept optional arguments as strings, using shell-like syntax:
`async combat.hunt goblin 12345`
And receive them as optional arguments:
async def hunt(ctx: ScriptContext, target: Optional[str] = None, room_id: Optional[str] = None) -> None:
...
These can be used anywhere Commands are accepted:
REPL: type
`async module.func_name`at the promptTrigger reply field: set reply as
`async module.func_name`Macro text: include
`async module.func_name`in macro textautowalk and autodiscover: include
`async module.func_name`in room-change command.
Stopping¶
Asynchronous programming is very useful but can also get out of control, and may accidentally flood the server with bad commands.
Scripts may be stopped at any time, by another script or by embedded commands,
`stopscript`Stop all running scripts. Each name is printed as it stops:
[stopscript] stopped: combat.hunt.This command is bound to Alt + Q by default.
`stopscript combat.hunt`Stop only the named script.
To stop everything immediately, press Alt + Q, a default macro for `stopscript`
Listing¶
`scripts`Show the names of all currently running scripts:
[scripts] running: combat.hunt [scripts] running: healer.top_up
If nothing is running:
[scripts] no scripts running.
Chaining¶
You can launch another script in the background by sending a command through ctx.send:
await ctx.send("`async hunt`")
Or run another script to completion:
await ctx.send("`await hunt`")
As python scripts, you may also just import and call functions directly:
import combat
await combat.hunt(ctx, "goblin")
See Scripting API for the full ctx API reference, including
Room and RoomStore class documentation.
Multi-condition waits¶
Sometimes you want to react to whichever thing happens first – the enemy
dies, you flee, or your HP drops too low. asyncio.wait() with
FIRST_COMPLETED handles this: give it a list of things to watch for, and
it returns as soon as one of them fires:
import asyncio
from telix.scripts import ScriptContext
async def hunt(ctx: ScriptContext, *args: str) -> None:
target = args[0] if args else "goblin"
await ctx.send(f"kill {target}")
done, pending = await asyncio.wait(
[
asyncio.ensure_future(ctx.wait_for(f"{target} has died")),
asyncio.ensure_future(ctx.wait_for("You flee")),
asyncio.ensure_future(ctx.condition_met("hp%", "<", 25)),
],
return_when=asyncio.FIRST_COMPLETED,
)
for t in pending:
t.cancel()
ctx.print("[hunt] done")
Complete examples¶
Room info script:
from telix.scripts import ScriptContext
async def run(ctx: ScriptContext) -> None:
room = ctx.room
if room is None:
ctx.print("[demo] No room data")
return
ctx.print(f"[demo] Room: {room.name} ({room.area})")
ctx.print(f"[demo] Exits: {', '.join(room.exits)}")
Travel to a room by number:
from telix.scripts import ScriptContext
async def goto(ctx: ScriptContext, *args: str) -> None:
if not args:
ctx.print("[rooms] Usage: `async rooms.goto <room_id>`")
return
path = ctx.find_path(args[0])
if path is None:
ctx.print(f"[rooms] No path to {args[0]}")
return
await ctx.send(";".join(path))
Wait for a pattern then react:
from telix.scripts import ScriptContext
async def hunt(ctx: ScriptContext, *args: str) -> None:
target = args[0] if args else "goblin"
await ctx.send(f"consider {target}")
m = await ctx.wait_for(r"seems? .* formidable|is? no match", timeout=5.0)
if not m or "no match" not in m.group(0):
ctx.print(f"[hunt] {target} too tough, skipping")
return
await ctx.send(f"kill {target}")
ctx.print("[hunt] fighting!")