Skip to content

Lua API Reference

Borys Stelmakh edited this page Sep 12, 2026 · 7 revisions

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.

Contents


Bake / modding

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.


Runtime triggers & ctx

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.

The ctx table

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.

NPCs (runtime)

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).

Spawning

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).

Behavior & stats

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.

Lifecycle, dialog & roster

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.

Quest book

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 engine's quest registry

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).

Sections, objects and names

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}).

State / vars

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.


Hero / player

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 hero's items

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.

Hero quest bits

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)).


Data tables

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.

Diagnostics / engine

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.

The npcobj wrapper

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

Constructors

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.

Identity & state

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.

Behavior archetypes

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.

Storyline & dialog

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).

Companions

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.

Talk detection

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).

Clone this wiki locally