-
-
Notifications
You must be signed in to change notification settings - Fork 3
Lua API Reference
The complete sacred.* Lua API, the ctx:* methods passed to trigger
handlers, and the npcobj NPC wrapper. Functions are grouped by topic, each
with a one-line description grounded in the SDK source.
Verified on Sacred Gold Steam, build 2.0.2.28 (2006-10-13).
The sacred table is available everywhere — in bake-time mod files and in
runtime callbacks. Most read/write functions need the world loaded (call them
from sacred.on_tick, sacred.on_world_load, or a trigger handler); when the
hero pointer isn't resolved yet (menu / loading) they return nil/false.
- Bake / modding
- Runtime triggers & ctx
- NPCs (runtime)
- Quest book
- Sections, objects and names
- State / vars
- Hero / player
- Data tables
- Diagnostics / engine
- The npcobj wrapper
File I/O and logging available during the bake (and at runtime). Paths are relative to the game install root.
| Function | Description |
|---|---|
sacred.log(msg) |
Write a line to sdk_loaded.log and the overlay ring. |
sacred.read_file(rel) |
Read any file under <game>/ and return its bytes. |
sacred.write_file(rel, bytes) |
Write bytes under custom/ (used by text.lua); returns byte count. |
package.path is pre-configured so require finds custom/lua/lib/?.lua first,
then the rest of your mod tree. package.cpath is deliberately empty (the SDK
won't load arbitrary DLLs from the script tree).
The bake-time builder libraries (quest, dialog, state, text, vanilla,
reward, questfsm, raw, funkcode, unsafe) are documented on
Writing Your First Mod and
Quests and Dialog Authoring.
Attach Lua callbacks that fire at game-time. See Writing Your First Mod.
| Function | Description |
|---|---|
sacred.on_trigger(name, fn) |
Register fn(ctx) to fire when Sacred dispatches trigger name. Handlers stack and run in registration order. |
sacred.clear_triggers() |
Drop all registered trigger handlers (for clean re-bake). |
sacred.on_tick(fn) |
fn() is pcalled ~every 250 ms while in-world — ideal for polling hero position / NPC state. |
sacred.on_world_load(fn) |
fn() fires once per process, the moment the FunkCode walker captures cQuestManager (a save is loaded). The place to call questbook_register. |
sacred.notify(text) |
Top-of-screen gold toast banner via the SDK overlay. Returns true if queued, false if throttled/deduped/empty. |
sacred.notify is anti-overflow: 256-byte cap, 750 ms minimum gap, back-to-back
duplicate dedup, queue capped at 8 (oldest evicted), each toast lives ~4.5 s.
Every on_trigger handler receives ctx as its first argument.
| Member | Returns | Notes |
|---|---|---|
ctx.trigger_name |
string | The name that fired. |
ctx:gold() |
int / nil | Hero gold; nil if hero pointer not built yet. |
ctx:give_gold(N) |
bool | Direct write to hero+0x3EE plus the coin/"+N" event; N<0 charges; clamped to 0..INT32_MAX. |
ctx:give_gold_silent(N) |
bool | Same money math, no UI/sound side effects. |
ctx:charge_gold(N) |
bool | Alias for give_gold(-N). |
ctx:has_item(res) |
bool / nil | Scans the 18 equip slots (backpack not yet covered). |
ctx:get_var(name) |
int / nil | First value of a named quest variable. |
ctx:set_var(name, v0[,v1,v2,v3]) |
bool | Overwrite a named quest variable (in-place). |
ctx:notify(text) |
bool | Forwards to sacred.notify. |
ctx:set_qbit(n,v) |
bool | Stub — use q.set_hero_qbit at bake time. |
ctx:get_qbit(n) |
bool / nil | Stub. |
Spawn living creatures, set behavior, equip, teleport, and bind to dialog at
game-time. The high-level way is the npcobj
wrapper — these are the raw bindings it sits on. See Runtime NPCs
for the full guide and the coordinate system. Handles are integers; positions
are KompassPos (kx, ky).
| Function | Description |
|---|---|
sacred.spawn_npc(type, kx, ky) |
Spawn a creature via the engine create path at KompassPos. Returns a handle or nil. |
sacred.spawn_here(type) |
Spawn at the hero's own valid position (no sector math). |
sacred.spawn_at(type, kx, ky) |
Spawn at KompassPos using the hero's sector — reliable near the player. |
sacred.spawn_item(type, kx, ky) |
Spawn a pickup-able ground item at KompassPos. Returns a handle. |
sacred.createnpc_engine(payload_bytes, want_type) |
Run a dev-authored CreateNPC record through the engine's own handler (type-correct HP/AI/faction). |
| Function | Description |
|---|---|
sacred.npc_info(handle) |
Returns { type, kx, ky, faction } or nil if the creature is gone. |
sacred.npc_set_faction(handle, value) |
Write the faction/side word (cCreature+0x1F4). |
sacred.npc_wake(handle) |
Activate the creature's AI. |
sacred.npc_set_stance(handle[, mode[, value]]) |
Set behavior stance (cCreature+0x1F0); mode 0 = class-default. |
sacred.npc_set_invulnerable(handle[, on=true]) |
Toggle essential/invulnerable. |
sacred.npc_set_stationary(handle[, on=true]) |
Hold post (no patrol) / free to roam. |
sacred.npc_set_level(handle, level) |
Set creature level/rank. |
sacred.npc_set_hp(handle, hp) |
Raise current + max HP (runtime spawns are weak by default). |
sacred.npc_make_combatant(handle[, level=20[, ai_class=3]]) |
Replay CreateNPC's combat init; ai_class 7 = active ally, 2 = monster, 13 = immune. |
sacred.npc_set_disposition(handle, matrix_class) |
Change hostility class mid-game and re-aggro. |
sacred.npc_equip(handle, item_type[, slot=0xC]) |
Give a visible item (experimental; guarded — returns false if the ABI guess misses). |
sacred.npc_teleport(handle, kx, ky) |
Engine teleport to KompassPos. |
| Function | Description |
|---|---|
sacred.npc_set_name(handle, "Captain Miles") |
Custom display name (no-op for a pure runtime spawn that lacks a DlgNPC entry). |
sacred.npc_quest_icon(handle[, on=true]) |
Floating "?!" quest-giver glyph over the head (visual only). |
sacred.npc_bind_quest(handle, "Name"[, marker_on=true]) |
Create the engine DlgNPC entry so name + overhead marker show; returns the index. |
sacred.dialog_arm(handle, "DlgName", "TEXT_KEY"[, "voice"]) |
Arm an NPC to speak baked text in the talk window. |
sacred.dialog_clear(handle) |
Close the armed dialog and clear the "?!" marker. |
sacred.npc_make_companion(handle) |
Make the NPC follow the hero, fight for him, treated as the hero for friend/foe. |
sacred.npc_dismiss(handle) |
Stop following; restore independent neutral AI. |
sacred.npc_despawn(handle) |
Clean engine removal (the DelNPC path). |
sacred.npc_roster_add(handle, quest_id) |
List the companion in the on-screen roster panel (crash-safe in-place add). |
sacred.npc_roster_remove(handle) |
Remove from the roster panel. |
sacred.npc_in_dialog(handle) |
Is the player talking to this NPC right now? (cCreature+0x200 bit 0x400). |
For native dialog text override in the talk window (dialog_override,
dialog_redirect, dialog_learn), see Dialog Text.
| Function | Description |
|---|---|
sacred.dialog_override(vanilla_name, our_name) |
Native by-name swap: when the engine resolves vanilla dialog node vanilla_name, render baked our_name instead. |
sacred.dialog_redirect(hash) |
Arm/disarm the native dialog-text redirect for the talk window; hash = sacred.hash(key) to arm, 0/nil to disarm. |
sacred.dialog_learn(hash) |
On talk-close, map the just-fetched vanilla key to hash and persist it for all future fetches. |
Read and write Sacred's quest-display registry at runtime. Register from
sacred.on_world_load; the registry is rebuilt from the save each load. See
Quests and Dialog Authoring.
| Function | Description |
|---|---|
sacred.questbook_register(quest_id) |
Append a brand-new display entry (calls the engine's vector::resize); returns the slot index. |
sacred.questbook_count() |
Number of entries in the display registry. |
sacred.questbook_get_id(idx) |
The quest_id at registry slot idx. |
sacred.questbook_set_log(quest_id, page, name0[, name1, ...]) |
Set the journal lines for an entry (page 0 = first tab); names are global.res resource names. |
sacred.questbook_add_log(quest_id, name) |
Append one journal line to the next free slot (0..10); for multi-step progression. |
sacred.questbook_set_marker(quest_id, worldX, worldY) |
Set the primary (story) minimap + world-map marker. |
sacred.questbook_set_kompass(quest_id, v0[, v1..v4]) |
Write the entry's kompass/position block (so the journal location preview renders). |
sacred.questbook_set_step_done(quest_id, done_bool) |
Fill (true) or hollow (false) the objective bullet. |
sacred.questbook_mark_solved(quest_id) |
Set the vanilla "solved" state (+0x04=100, kompass cleared). |
sacred.questbook_complete(quest_id) |
Remove the entry from the journal entirely. |
sacred.questbook_dump() |
Diagnostic: log every registry entry's fields. |
sacred.hide_other_quests() |
Hide all non-SDK quests from the journal/map this tick (idempotent; call from on_tick). Returns hidden count. |
sacred.hide_vanilla_quests([on=true]) |
Persistently suppress every non-SDK quest at journal-build time (race-free); call once. |
sacred.questbook_track(quest_id [, handle]) |
Put this quest in its compass column (story column for ids ≤ 99, side column above), optionally following a creature. |
sacred.questbook_clear_marker() |
Clear the forced primary arrow (the engine's slot-3 marker). |
The registry the game itself drives — see Native Quests. These
are what nativequest.lua uses; a quest id in the registry also makes the
SetUpQuest / TriggerQuest / ExitQuest / LoseQuest records work on it.
| Function | Description |
|---|---|
sacred.quest_register(id, name) |
Put id in the engine's quest registry (id must be positive). The entry is re-created after every world load and savegame load. |
sacred.quest_state(id) |
The live entry: { index, name, flags, setup, entered, done, trigger, on_enter, on_setup, on_exit, on_lose, sdk }, or nil. |
sacred.quest_flags(id [, set, clear]) |
Read, or edit, the registry flags of one of your quests (bit 0 set up, 1 done, 2 entered). |
The SDK's own script sections, and the engine objects a record can address. See Vanilla Verbs and World Objects.
| Function | Description |
|---|---|
sacred.section_define(name, bytes [, owner_quest]) |
Define (or replace) an SDK section from record bytes. It enters the engine's section table on the next tick and is found by name like a shipped one. owner_quest reinstates the engine's gate: the section stays dead until that quest is set up. |
sacred.section_run(name [, handle]) |
Run a section — yours or a shipped one — on the next heartbeat. With a handle, the object behind it becomes the section's context (what a record means by "this object"). |
sacred.name_register(name) |
Give a new object name an id through the engine's own registrar (89 dynamic slots). Without it a CreateObj name is dropped and nothing can address the object. |
sacred.object_by_name(name) |
The handle of a named world object. |
sacred.object_at(type, kx, ky [, radius=2]) |
The nearest object of that type within radius tiles — no name needed. |
sacred.kill_counters() / sacred.collect_counters()
|
The engine's live objective counters ({type, left, total, section}). |
Sacred's named-state store (hq_uw, dq_belohnung, custom vars from q.var()),
backed by the cQuestManager array. Values persist inside the save game. See
examples/09_state_vars.lua.
| Function | Description |
|---|---|
sacred.state_get(name) |
Returns the full {v1, v2, v3, v4} value array, or nil. |
sacred.state_set(name, v0[, v1, v2, v3]) |
Overwrite a named state value in-place; returns bool. |
sacred.state_dump() |
Log every named-state entry; returns the count. |
The ctx:get_var / ctx:set_var methods are the convenience form (first value
as an integer) — see the ctx table.
| Function | Description |
|---|---|
sacred.hero_pos() |
Hero world coords x, y (KompassPos space), or nil if not resolved. |
sacred.give_gold(N) |
Global form of ctx:give_gold — usable from on_tick/on_world_load; N>0 grants, N<0 charges. |
sacred.set_spawn(kx, ky) |
Teleport the active hero to KompassPos now; returns bool (false until the hero chain resolves). |
sacred.arm_spawn_teleport(kx, ky) |
Arm a one-shot rewrite of the engine's own campaign start placement to (kx, ky). Call from on_world_load. |
sacred.read_save(path) |
Read a hero .pax save; returns { class_id, class_name, level, gold, xp, underworld } or nil. |
sacred.hero_weapon_dump() |
Diagnostic: log the hero's equip slots + item types. |
The engine's own inventory: the 19 equipment slots, the backpack grid and the
quest-item list. reward.lua wraps these for the common cases.
| Function | Description |
|---|---|
sacred.hero_items() |
Every item the hero carries: { where = "equip"/"bag"/"quest", slot, ref, type }. |
sacred.hero_item_count(type) |
How many of that item type the hero has, anywhere. |
sacred.hero_has_item(type) |
True if the hero has at least one. |
sacred.hero_put_item(type) |
Create one and put it in the backpack, through the engine's own putItem — a normal item the player can use, sell or drop. |
sacred.hero_take_item(type [, n=1]) |
Take items back, the way the engine's own DelOBJ does (held, worn or in the bag). Returns how many went. |
reward.give_quest_item(type) uses a different route (the CreateObj quest-item
operand): it lands on the quest-item list, not in the backpack.
The per-hero, per-difficulty bit array in cStatsManager — it travels with the
hero rather than with the world, which makes it the right place for "this hero
has already done this".
| Function | Description |
|---|---|
sacred.hero_qbit(n) |
Read bit n on the current difficulty. |
sacred.hero_qbit_set(n, on) |
Write it. |
sacred.difficulty() |
The current difficulty index. |
Vanilla uses bits 0..11 and 101..106; the SDK keeps to 150..159. The engine's own
SetVarBit record with the name "HeroQBit" writes the same array
(Vb.set_var_bit("HeroQBit", n)).
Read-only lookups against the SDK's compiled-in port of Sacred's data tables. No game state required — usable any time, including at bake.
| Function | Description |
|---|---|
sacred.creature_name(id) |
Creature type name; returns name, note, band (band = hero/monster/humanoid_npc/animal/townsfolk). |
sacred.combat_art(packed_id) |
Combat-art name; returns name, class_name, note. |
sacred.companions(class_id) |
Companion info for hero class 1..9 (only 3/4/5/6 have one): { class_name, model_res, name_res={...} }. |
sacred.class_skills(class_id) |
Per-class skill layout for class 1..9: list of { slot, id, name } (empty slots skipped). |
sacred.skill_name(id) |
English skill name for id 0..33. |
sacred.xp_for_level(level) |
Cumulative XP to reach 1-based level (1..206). |
sacred.survival_bonus(deaths) |
Survival bonus percentage (0..99). |
sacred.bonus_name(packed_id) |
Item-bonus name (German). |
sacred.hash(name) |
Sacred's resource-name hash of name, masked to 31 bits. |
Read-only instrumentation for SDK contributors and live RE. Most log to
sdk_loaded.log; the *_dump family logs and returns nothing useful to a mod.
| Function | Description |
|---|---|
sacred.scan_creatures([filter_type=-1[, cap=800]]) |
Log live creatures in the world, optionally filtered by type. |
sacred.npc_dump(handle[, tag]) |
Log render-suspect fields of one creature. |
sacred.npc_peek(handle) |
Read-only SEH-guarded field reads; returns v14, v245, vC, v204 (diagnostic). |
sacred.roster_dump([tag]) |
Log the companion-roster array (qm+0x31c) (diagnostic). |
sacred.trigger_table_dump([want_name]) |
Walk and log the engine's trigger-name table; returns the entry count (diagnostic). |
sacred.dump_vanilla_of(type, ai_class[, skip_lo[, skip_hi]]) |
Log a vanilla CreateNPC record template for the given type/ai_class (diagnostic). |
sacred.arm_hwbp(handle[, offset=0xc]) |
Arm a hardware data breakpoint on a creature field via DR0/VEH — must be called from on_tick (deep RE; read-only). |
sacred.resolve_engine() |
Resolve/verify engine VAs now; returns { inflate, inflate_init, debug_log, resolved } (diagnostic). |
sacred.globalres(handle) |
Resolve a global.res string handle to its UTF-8 text via the engine's own resolver; nil on miss. |
require "npcobj" returns an OOP wrapper around a spawned creature handle —
the high-level, recommended way to work with runtime NPCs. spawn returns an
object you can stash in a global or quest table; the handle stays valid for the
creature's lifetime, and :alive() tells you if it's still there.
local N = require "npcobj"
local NPC = require "npc" -- 474 creature-type constants
local guard = N.spawn(NPC.SKELETON, 2793, 2273)
guard:make_guard(30) -- ally soldier: awake + class-default + AI
guard:save("townguard") -- persist under a name; N.get("townguard") later| Method | Description |
|---|---|
N.spawn(type, kx, ky) |
Spawn at KompassPos via the hero-sector path; returns an Npc or nil, reason. |
N.spawn_here(type) |
Spawn at the hero (no coords). |
N.spawn_template(arch, opts) |
THE recommended spawn: build a dev-authored CreateNPC record from npc_templates.lua and let the engine create+init it (type-correct stats). |
N.spawn_item(item_type, kx, ky) |
Spawn a ground item; returns the raw handle (items aren't cCreatures). |
N.wrap(handle) |
Wrap an existing numeric handle. |
N.get(name) / N.forget(name)
|
Look up / drop a saved NPC in the named registry. |
| Method | Description |
|---|---|
o:handle() |
The raw integer handle. |
o:info() |
{ type, kx, ky, faction } or nil. |
o:alive() |
Handle still resolves to a live creature? |
o:type() |
Creature type id. |
o:pos() |
KompassPos kx, ky. |
o:faction() / o:set_faction(v)
|
Get / set the side word. |
o:save(name) |
Persist in the module registry; returns self for chaining. |
| Method | Description |
|---|---|
o:wake() |
Turn on the AI. |
o:stance(mode, val) |
Set behavior stance (mode 0 = class-default). |
o:activate() |
Class-default stance + AI on (the one-call "behave like your kind"). |
o:set_level(n) / o:set_hp(n)
|
Level / current+max HP. |
o:make_aggressive(level) |
Hunts the hero on sight (explicit stance 2 + awake bit + AI). |
o:make_hostile(fac, level) |
Class-natural: only fights back when hit. |
o:make_friendly(fac) |
Friendly side + class-default behavior. |
o:make_combatant(level, ai_class) |
Replay the engine combat init (ai_class 7 = active ally, 2 = monster, 13 = immune). |
o:make_soldier(level, hp) |
A tough ally DEFENDER that fights monsters, never the hero. |
o:make_guard(level) |
The vanilla ally-guard combo (awake + class-default; patrols and defends). |
o:make_immortal_passive(level) |
Passive, immortal, holds post (captain type). |
o:set_invulnerable(on) |
Essential / invulnerable. |
o:set_stationary(on) |
Hold post vs. roam. |
| Method | Description |
|---|---|
o:set_name(name) / o:name()
|
Custom display name. |
o:quest_icon(on) |
Floating "?!" glyph. |
o:teleport(kx, ky) / o:walk_to(kx, ky)
|
Engine teleport to a point. |
o:bind_quest(name, icon_on) |
Create the engine DlgNPC entry (name + overhead marker); returns the index. |
o:make_quest_giver(name, level) |
One call: immortal + holds post + bind (a walk-up-and-talk NPC). |
o:say(text_key, vanilla_node, voice) |
Speak baked text; with vanilla_node does a native by-name swap (see Dialog Text). |
o:dialog_off() |
Close the armed dialog and clear the marker. |
o:equip(item_type, slot) |
Give a visible item (slot default 0x0D = main hand). |
| Method | Description |
|---|---|
o:make_companion(quest_id) |
Party-follow + fights for the hero; pass quest_id to also list it in the roster panel. |
o:dismiss() |
Stop following, restore neutral AI, leave the panel. |
o:despawn() |
Clean engine removal (the Npc is dead after this). |
o:set_disposition(d) |
'hostile' / 'ally' / 'neutral'; re-aggros immediately. |
| Method | Description |
|---|---|
o:in_dialog() |
Is the player talking to this NPC right now? |
o:on_talk(fn) |
Rising-edge: fn(self) once each time the player starts talking. Re-arms across death/respawn. |
o:on_talk_end(fn) |
Falling-edge: fn(self) once each time the talk window closes (use for effects that should land after the player reads the line). |
Getting started
Authoring
- Quests and Dialog Authoring
- Native Quests
- Runtime NPCs
- Hero Classes
- Dialog Text
- Dialog Nodes (catalogue)
The engine's own verbs
Reference