Skip to content

Cut Scenes

Borys Stelmakh edited this page Sep 14, 2026 · 3 revisions

Cut Scenes

The engine can queue commands on an actor and play them out in order — an NPC walks somewhere and then does something, with no Lua in between. Sacred ships two such scenes; this page says exactly how much of that machinery an SDK mod can use, because part of it stays out of reach.

Verified on Sacred Gold Steam, build 2.0.2.28 (2006-10-13), measured over four live runs against the actor's own command queue.

Contents

The idea

A scene is one section: cinema mode on, the actor commands, cinema mode off. While cinema mode is on a command is not executed — it is queued on the actor it names, and the actors play their queues out in order. That is what turns a pile of records into a sequence.

Writing one

local Sc = require "scene"

Sc.play(
  Sc.walk_to("res:GUIDE", { sacred.hero_pos() }),   -- coordinates, not "HERO"
  Sc.anim("res:GUIDE", 89))
Step Record Works from an SDK section?
Sc.walk_to(who, {x, y}) NPC_Goto yes — queues and walks.
Sc.walk_to(who, "HERO") NPC_Goto no (see below).
Sc.anim(who, id) PlayAnim yes — queues and plays in turn.
Sc.teleport(who, pos) Teleport same queue kind as the two above.
Sc.attack(who, target) Attack same queue kind.
Sc.fx(who, argb, fx) Partikel same queue kind.
Sc.focus(who) SetFocus the camera; the addon's intro opens with it.
Sc.wait_for(who, target) / Sc.wait(who, n) Wait no.
Sc.talk(who, node) NPC_TalkTo no — use Sc.say instead.

Other helpers: Sc.open() / Sc.close() run cinema mode on and off as separate sections (the addon does exactly that — one section opens it, a timer section closes it), Sc.queue(...) sends steps between them, Sc.abort() closes cinema mode by hand, Sc.is_on() reads the engine's flag, and Sc.queued(handle) returns how many commands an actor still has pending.

What queues and what does not

Measured, every 250 ms, against the actor's queue:

  • Walking to coordinates and playing an animation queue and run in order. The queue climbed to 2 and drained while the actor walked tile by tile and then gestured on arrival. That is a real cut scene.
  • Walking to a creature does nothing — no queue entry, no step — even though both shipped scenes use that form. Pass coordinates.
  • Wait and NPC_TalkTo never reach the queue, in any arrangement: one section with everything, or one record per section, cinema mode opened inline or separately. The Wait handler wants more than the record — two resolved ids and a register the walker carries in from the record before it, which a section of ours does not reproduce.

So sequence the rest from Lua: a tick counter, or simply Sc.play for the two steps that do queue.

The two cinema records, and a theory that did not survive the log

There are two records that open the queue, and they are not the same:

Record Tag What it sets
SetCinemaMode 0x86 quest-manager flag bits 0 and 1, kernel event 0x13 {3,1}
SetAnimMode 0x58 bit 0 only, the same kernel event

Reading the record walker suggests a trap in 0x86: when the hero's action queue is empty it calls into the creature layer and returns from the walker, which would abandon every record after it — exactly the shape of "the scene did nothing". Measured 2026-09-12, it does not happen to us: both records queued the walk and the animation (queue 0 → 2) and the section ran to its very last record. The early exit reads the queue of the section's own creature, and an SDK section runs with no creature at all, so the branch is never taken.

Vb.anim_mode() (0x58) is in the SDK anyway: it is the smaller hammer — the queue without the camera — when all you want is an NPC to perform.

Making an NPC speak

Sc.say(node) opens a dialog node's window through the Popup record. It needs no NPC, works from anywhere, and shows the same window NPC_TalkTo would have opened — so an NPC that walks up and then "speaks" is:

Sc.play(Sc.walk_to(GUIDE, { sacred.hero_pos() }), Sc.anim(GUIDE, 89))
-- a few seconds later, from your own tick:
Sc.say("MY_GUIDE_NODE")          -- the node name you gave Npc:bind_quest

Popup binds the node to the hero for the length of the window, the way a signpost does. Live: an NPC answered a paid bribe this way, right after the button.

When nobody speaks at all — the hero's own thoughts, a note found on the ground — Sc.note declares a node of its own the first time (the vanilla DlgNPC record, Vb.declare_node) and opens it the same way. Live.

require("text").named("AXE_NOTE", "The axe is covered in blood, and the tracks lead east.")
Sc.note("my_axe_note", "AXE_NOTE", function() ... end)   -- on_ok is optional

Safety

An open cinema mode swallows actor commands, so:

  • Sc.play always closes it in the same section;
  • Sc.abort() closes it by hand if a scene was ever interrupted;
  • Sc.is_on() tells you (it reads the engine's own flag), so a watchdog on your tick can close it after a timeout.

The shipped scenes

Worth reading before writing your own:

  • Daemonin_Tutorial and btn_Deamonstart (base script of the Seraphim class) — the minimal shape: cinema on, walk, wait, talk, cinema off.
  • Los_kampf1_anim (addon, Seraphim) — the full treatment: the actor's dialog unbound, the camera put on the hero, particles, an animation, thunder played on the actor, barriers and triggers built around the arena so the player cannot walk out, and cinema mode closed later from a timer section.

Clone this wiki locally