Files
dusk/.claude/rpg/cutscene.md
T
2026-06-18 14:59:21 -05:00

4.5 KiB

Cutscenes

Two distinct layers: a low-level engine sequencer (src/dusk/cutscene/) and a higher-level RPG wrapper (src/dusk/rpg/cutscene/). Almost all game code works with the RPG layer.


Engine sequencer (src/dusk/cutscene/)

cutscene_t CUTSCENE is a minimal event runner with up to CUTSCENE_EVENT_COUNT_MAX (16) cutsceneevent_t slots. Each event has three callbacks:

typedef struct {
  errorret_t (*onStart)(void);
  errorret_t (*onUpdate)(void);
  errorret_t (*onEnd)(void);
} cutsceneevent_t;

API:

cutscenePlay(events, count);   // copy events array and start from index 0
cutsceneAdvance();             // end current event, start next (deactivates after last)
cutsceneStop();                // abort immediately
cutsceneIsActive();            // bool

This layer is primarily used by the RPG cutscene system — game code doesn't normally touch it directly.


RPG cutscene layer (src/dusk/rpg/cutscene/)

Data structures

A cutscene_t is just a pointer to an item array and a count:

typedef struct cutscene_s {
  const cutsceneitem_t *items;
  uint8_t itemCount;
} cutscene_t;

A cutsceneitem_t is a tagged union of all item types:

typedef struct cutsceneitem_s {
  cutsceneitemtype_t type;
  union {
    cutscenetext_t    text;      // display text in textbox
    cutscenecallback_t callback; // call a void(*)(void) function
    cutscenewait_t    wait;      // pause for a fixed_t duration (seconds)
    const cutscene_t *cutscene;  // nest another cutscene
  };
} cutsceneitem_t;

Item types

Type constant Payload Behaviour
CUTSCENE_ITEM_TYPE_TEXT cutscenetext_ttext[256] + rpgtextboxpos_t position Shows textbox; advances on player confirm input
CUTSCENE_ITEM_TYPE_CALLBACK cutscenecallback_t (function pointer) Calls the function once, then immediately advances
CUTSCENE_ITEM_TYPE_WAIT cutscenewait_t (a fixed_t in seconds) Counts down animTime each frame, then advances
CUTSCENE_ITEM_TYPE_CUTSCENE const cutscene_t * Plays the nested cutscene before continuing

Runtime state

cutscenesystem_t CUTSCENE_SYSTEM tracks:

  • scene — pointer to the active cutscene_t
  • currentItem — index into scene->items[]
  • data — per-item runtime data (cutsceneitemdata_t, currently just cutscenewaitdata_t)
  • mode — the current cutscenemode_t

API:

cutsceneSystemStartCutscene(cutscene);   // begin playing a cutscene
cutsceneSystemNext();                    // advance to next item
cutsceneSystemUpdate();                  // called each frame from rpgUpdate
cutsceneSystemGetCurrentItem();          // inspect active item

Cutscene mode (cutscenemode.h)

Each item can run in one of three modes:

CUTSCENE_MODE_NONE           // no cutscene active
CUTSCENE_MODE_FULL_FREEZE    // pause everything (not yet used)
CUTSCENE_MODE_INPUT_FREEZE   // player input blocked (default: CUTSCENE_MODE_INITIAL)
CUTSCENE_MODE_GAMEPLAY       // player can still move during cutscene

cutsceneModeIsInputAllowed() is checked by entityUpdate() before invoking the movement callback — the player cannot walk when in INPUT_FREEZE mode.

Defining a cutscene

Cutscenes are defined as static const arrays in header files under rpg/cutscene/scene/. Example (testcutscene.h):

static const cutsceneitem_t MY_CUTSCENE_ITEMS[] = {
  {
    .type = CUTSCENE_ITEM_TYPE_TEXT,
    .text = { .text = "Hello!", .position = RPG_TEXTBOX_POS_BOTTOM }
  },
  {
    .type = CUTSCENE_ITEM_TYPE_WAIT,
    .wait = FIXED(1.5f)
  },
  {
    .type = CUTSCENE_ITEM_TYPE_CUTSCENE,
    .cutscene = &ANOTHER_CUTSCENE
  },
};

static const cutscene_t MY_CUTSCENE = {
  .items = MY_CUTSCENE_ITEMS,
  .itemCount = sizeof(MY_CUTSCENE_ITEMS) / sizeof(cutsceneitem_t)
};

Attach to an NPC via its interact component:

entity->interact.type = ENTITY_INTERACT_CUTSCENE;
entity->interact.data.cutscene = &MY_CUTSCENE;

Textbox (src/dusk/rpg/rpgtextbox.h)

rpgtextbox_t RPG_TEXTBOX is the global textbox state:

typedef struct {
  rpgtextboxpos_t position;      // RPG_TEXTBOX_POS_TOP or RPG_TEXTBOX_POS_BOTTOM
  bool_t visible;
  char_t text[RPG_TEXTBOX_MAX_CHARS];   // 256 chars
} rpgtextbox_t;

API:

rpgTextboxShow(position, text);   // copies text, sets visible = true
rpgTextboxHide();                 // sets visible = false
rpgTextboxIsVisible();            // bool

The textbox state is read by ui/uitextbox.c during the UI render pass to draw the dialogue box on screen. rpgtextbox.c itself does no rendering.