Rework CUTSCENE_ITEM_TYPE_INSERT to splice into the running scene's item array

Replaces the separate insert buffer/scene and insertStack return-frame
mechanism with cutsceneAppend/cutsceneAppendNext, so an inserted
cutscene's items become part of the running scene's own array instead
of a jump-and-return. Moves the loaded-items buffer/scene from file-scope
globals into cutscenesystem_t, and extracts shared start-cutscene setup
into cutsceneSystemPrepare.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
This commit is contained in:
2026-09-17 22:26:36 -05:00
co-authored by Claude Sonnet 5
parent 44594f7011
commit 5910110b95
22 changed files with 548 additions and 582 deletions
+25 -16
View File
@@ -86,9 +86,10 @@ errorret_t cutsceneParseDoc(
// Everything structural has validated by this point, so it's safe to
// start overwriting items - a per-item parse failure past this point
// still leaves it partially overwritten, but that's fine: any load
// failure here already sends the game into the fatal-error overlay
// (see cutsceneSystemLoad/cutsceneCutsceneResolve/cutsceneInsertResolve),
// so nothing keeps running against stale data.
// failure here already sends the game into the fatal-error overlay (see
// cutsceneSystemLoad/cutsceneCutsceneResolve/
// cutsceneInsertResolveAndSplice), so nothing keeps running against
// stale data.
memoryZero(items, itemCount * sizeof(cutsceneitem_t));
yyjson_arr_iter itemIter = yyjson_arr_iter_with(itemsArr);
@@ -101,24 +102,14 @@ errorret_t cutsceneParseDoc(
}
scene->itemCount = (uint8_t)itemCount;
scene->itemsMax = itemsMax;
scene->pause = pause;
errorOk();
}
void cutsceneAppend(
void cutsceneAppendAt(
cutscene_t *scene,
const size_t itemsMax,
const cutsceneitem_t *items,
const uint8_t count
) {
assertNotNull(scene, "Scene cannot be NULL");
cutsceneAppendNext(scene, itemsMax, scene->itemCount, items, count);
}
void cutsceneAppendNext(
cutscene_t *scene,
const size_t itemsMax,
const uint8_t atIndex,
const cutsceneitem_t *items,
const uint8_t count
@@ -128,7 +119,7 @@ void cutsceneAppendNext(
assertTrue(count > 0, "Count cannot be 0");
assertTrue(atIndex <= scene->itemCount, "atIndex out of bounds");
assertTrue(
(size_t)scene->itemCount + count <= itemsMax,
(size_t)scene->itemCount + count <= scene->itemsMax,
"Cutscene item append exceeds itemsMax"
);
@@ -143,3 +134,21 @@ void cutsceneAppendNext(
memoryCopy(&scene->items[atIndex], items, count * sizeof(cutsceneitem_t));
scene->itemCount += count;
}
void cutsceneAppend(
cutscene_t *scene,
const cutsceneitem_t *items,
const uint8_t count
) {
assertNotNull(scene, "Scene cannot be NULL");
cutsceneAppendAt(scene, scene->itemCount, items, count);
}
void cutsceneAppendNext(
cutscene_t *scene,
const uint8_t currentIndex,
const cutsceneitem_t *items,
const uint8_t count
) {
cutsceneAppendAt(scene, (uint8_t)(currentIndex + 1), items, count);
}
+54 -31
View File
@@ -12,6 +12,13 @@
typedef struct cutscene_s {
cutsceneitem_t *items;
uint8_t itemCount;
// Capacity of items - see cutsceneAppend/cutsceneAppendAt/
// cutsceneAppendNext, which assert against this rather than taking a
// capacity parameter of their own. Set once, either by cutsceneParseDoc
// (to whatever itemsMax it was given) or by whoever declares a
// C-authored cutscene_t directly (to items' true array capacity, which
// may be larger than itemCount to leave room for a later append/insert).
size_t itemsMax;
cutscenepause_t pause;
} cutscene_t;
@@ -47,15 +54,17 @@ size_t cutsceneCountItems(yyjson_doc *doc);
/**
* Parses doc's "items" (and optional "pause") into items, and updates
* scene's itemCount/pause - scene->items is left untouched, since the
* caller already points it at items. Purely a parser: doesn't touch the
* asset system, doesn't lock/load anything, and doesn't take ownership of
* doc - the caller is responsible for both loading doc beforehand and for
* its lifetime afterward (name/string fields written into items point
* directly into it, so it must outlive items' use).
* scene's itemCount/itemsMax/pause - scene->items is left untouched, since
* the caller already points it at items. Purely a parser: doesn't touch
* the asset system, doesn't lock/load anything, and doesn't take
* ownership of doc - the caller is responsible for both loading doc
* beforehand and for its lifetime afterward (though as of cutscene items
* owning their own string fields by value, nothing parsed here actually
* needs doc to survive past this call returning).
*
* @param doc Already-parsed JSON document to read from.
* @param scene Destination scene - itemCount/pause are overwritten.
* @param scene Destination scene - itemCount/itemsMax/pause are
* overwritten.
* @param items Destination item array, capacity itemsMax.
* @param itemsMax Capacity of items.
* @return Error code indicating success or failure of the parse.
@@ -68,39 +77,53 @@ errorret_t cutsceneParseDoc(
);
/**
* Appends items onto the end of scene->items, growing scene->itemCount.
* scene->items must point at a buffer with capacity itemsMax - asserts if
* appending would exceed it.
*
* @param scene Cutscene to append to.
* @param itemsMax Capacity of scene->items.
* @param items Items to append, copied in order.
* @param count Number of entries in items.
*/
void cutsceneAppend(
cutscene_t *scene,
const size_t itemsMax,
const cutsceneitem_t *items,
const uint8_t count
);
/**
* Same as cutsceneAppend, but inserts items starting at atIndex instead of
* at the end - e.g. pass CUTSCENE_SYSTEM.currentItem + 1 to make these
* items run immediately after the currently-executing one. Every existing
* item from atIndex onward shifts right by count to make room.
* Inserts items into scene->items starting at atIndex, growing
* scene->itemCount. Every existing item from atIndex onward shifts right
* by count to make room. Asserts if this would exceed scene->itemsMax.
*
* @param scene Cutscene to insert into.
* @param itemsMax Capacity of scene->items.
* @param atIndex Index the first inserted item should end up at - must be
* <= scene->itemCount.
* @param items Items to insert, copied in order.
* @param count Number of entries in items.
*/
void cutsceneAppendNext(
void cutsceneAppendAt(
cutscene_t *scene,
const size_t itemsMax,
const uint8_t atIndex,
const cutsceneitem_t *items,
const uint8_t count
);
/**
* Same as cutsceneAppendAt, but always inserts at the end of
* scene->items.
*
* @param scene Cutscene to append to.
* @param items Items to append, copied in order.
* @param count Number of entries in items.
*/
void cutsceneAppend(
cutscene_t *scene,
const cutsceneitem_t *items,
const uint8_t count
);
/**
* Same as cutsceneAppendAt, but inserts right after currentIndex (i.e. at
* currentIndex + 1) instead of at a literal index - e.g. pass
* CUTSCENE_SYSTEM.currentItem to make these items run immediately after
* the currently-executing one. currentIndex may be 0xFF (nothing has run
* yet), which wraps to insert at index 0, matching that sentinel's usual
* meaning elsewhere in the cutscene system.
*
* @param scene Cutscene to insert into.
* @param currentIndex Index to insert after.
* @param items Items to insert, copied in order.
* @param count Number of entries in items.
*/
void cutsceneAppendNext(
cutscene_t *scene,
const uint8_t currentIndex,
const cutsceneitem_t *items,
const uint8_t count
);
+117 -159
View File
@@ -16,38 +16,32 @@
cutscenesystem_t CUTSCENE_SYSTEM;
cutsceneitem_t CUTSCENE_LOADED_ITEMS[CUTSCENE_LOADED_ITEMS_MAX];
cutscene_t CUTSCENE_LOADED_SCENE = {
.items = CUTSCENE_LOADED_ITEMS,
.itemCount = 0,
.pause = CUTSCENE_PAUSE_NONE
};
static yyjson_doc *CUTSCENE_LOADED_DOC = NULL;
void cutsceneLoadedSetDoc(yyjson_doc *doc) {
if(CUTSCENE_LOADED_DOC != NULL) yyjson_doc_free(CUTSCENE_LOADED_DOC);
CUTSCENE_LOADED_DOC = doc;
}
cutsceneitem_t CUTSCENE_INSERT_ITEMS[CUTSCENE_INSERT_ITEMS_MAX];
cutscene_t CUTSCENE_INSERT_SCENE = {
.items = CUTSCENE_INSERT_ITEMS,
.itemCount = 0,
.pause = CUTSCENE_PAUSE_NONE
};
static yyjson_doc *CUTSCENE_INSERT_DOC = NULL;
void cutsceneInsertSetDoc(yyjson_doc *doc) {
if(CUTSCENE_INSERT_DOC != NULL) yyjson_doc_free(CUTSCENE_INSERT_DOC);
CUTSCENE_INSERT_DOC = doc;
}
void cutsceneSystemInit() {
memoryZero(&CUTSCENE_SYSTEM, sizeof(cutscenesystem_t));
CUTSCENE_SYSTEM.loadedScene.items = CUTSCENE_SYSTEM.loadedItems;
CUTSCENE_SYSTEM.loadedScene.itemsMax = CUTSCENE_LOADED_ITEMS_MAX;
}
void cutsceneSystemDispose() {
CUTSCENE_SYSTEM.scene = NULL;
CUTSCENE_SYSTEM.currentItem = 0xFF;
CUTSCENE_SYSTEM.pause = CUTSCENE_PAUSE_NONE;
CUTSCENE_SYSTEM.entityInteract = NULL;
CUTSCENE_SYSTEM.entityInteracted = NULL;
CUTSCENE_SYSTEM.entityLastCreated = NULL;
CUTSCENE_SYSTEM.entityLastRef = NULL;
CUTSCENE_SYSTEM.areaLastCreated = CUTSCENE_AREA_LAST_CREATED;
CUTSCENE_SYSTEM.textMiniLastCreated = CUTSCENE_TEXT_MINI_LAST_CREATED;
CUTSCENE_SYSTEM.textCache[0] = '\0';
CUTSCENE_SYSTEM.onComplete = NULL;
cutsceneLoadedSetDoc(NULL);
}
void cutsceneSystemPrepare(
const cutscene_t *cutscene,
cutscene_t *cutscene,
entity_t *interact,
entity_t *interacted
) {
@@ -62,15 +56,14 @@ void cutsceneSystemPrepare(
CUTSCENE_SYSTEM.textCache[0] = '\0';
CUTSCENE_SYSTEM.currentItem = 0xFF;// Set to 0xFF so Next wraps to 0.
CUTSCENE_SYSTEM.onComplete = NULL;
CUTSCENE_SYSTEM.insertStackSize = 0;
}
void cutsceneSystemStartCutscene(const cutscene_t *cutscene) {
void cutsceneSystemStartCutscene(cutscene_t *cutscene) {
cutsceneSystemStartCutsceneWith(cutscene, NULL, NULL);
}
void cutsceneSystemStartCutsceneWith(
const cutscene_t *cutscene,
cutscene_t *cutscene,
entity_t *interact,
entity_t *interacted
) {
@@ -79,13 +72,26 @@ void cutsceneSystemStartCutsceneWith(
}
void cutsceneSystemStartCutsceneAndGoToMarker(
const cutscene_t *cutscene,
cutscene_t *cutscene,
const char_t *marker
) {
cutsceneSystemPrepare(cutscene, NULL, NULL);
cutsceneGoTo(marker);
}
void cutsceneSystemInsertCutscene(const cutscene_t *cutscene) {
assertNotNull(
CUTSCENE_SYSTEM.scene,
"cutsceneSystemInsertCutscene called with no cutscene running"
);
cutsceneAppendNext(
CUTSCENE_SYSTEM.scene, CUTSCENE_SYSTEM.currentItem,
cutscene->items, cutscene->itemCount
);
cutsceneSystemNext();
}
void cutsceneSystemLoad(const char_t *file) {
char_t path[ASSET_FILE_NAME_MAX];
stringFormat(path, sizeof(path), "cutscenes/%s", file);
@@ -100,8 +106,7 @@ void cutsceneSystemLoad(const char_t *file) {
}
// Steal the parsed doc before unlocking - assetJsonDispose would
// otherwise free it out from under CUTSCENE_LOADED_ITEMS' name pointers
// the moment this entry gets reaped.
// otherwise free it out before it's been parsed below.
yyjson_doc *doc = entry->data.json;
entry->data.json = NULL;
assetUnlockEntry(entry);
@@ -110,12 +115,12 @@ void cutsceneSystemLoad(const char_t *file) {
// trigger a reap - assetRequireLoaded treats an already-LOADED entry as
// an instant cache hit with no reparsing, so a stale leftover entry for
// this same path would otherwise make a later load of it wrongly skip
// reloading, still returning CUTSCENE_LOADED_SCENE even after some
// other cutscene has since overwritten it.
// reloading, still returning CUTSCENE_SYSTEM.loadedScene even after
// some other cutscene has since overwritten it.
assetReapUnused();
errorret_t parseResult = cutsceneParseDoc(
doc, &CUTSCENE_LOADED_SCENE, CUTSCENE_LOADED_ITEMS,
doc, &CUTSCENE_SYSTEM.loadedScene, CUTSCENE_SYSTEM.loadedItems,
CUTSCENE_LOADED_ITEMS_MAX
);
if(errorIsNotOk(parseResult)) {
@@ -126,7 +131,12 @@ void cutsceneSystemLoad(const char_t *file) {
}
cutsceneLoadedSetDoc(doc);
cutsceneSystemStartCutscene(&CUTSCENE_LOADED_SCENE);
cutsceneSystemStartCutscene(&CUTSCENE_SYSTEM.loadedScene);
}
void cutsceneLoadedSetDoc(yyjson_doc *doc) {
if(CUTSCENE_LOADED_DOC != NULL) yyjson_doc_free(CUTSCENE_LOADED_DOC);
CUTSCENE_LOADED_DOC = doc;
}
void cutsceneRestart(void) {
@@ -146,88 +156,6 @@ void cutsceneRestart(void) {
CUTSCENE_SYSTEM.onComplete = onComplete;
}
void cutsceneSystemUpdate() {
if(CUTSCENE_SYSTEM.scene == NULL) return;
const cutsceneitem_t *item = cutsceneSystemGetCurrentItem();
if(cutsceneItemUpdate(item, &CUTSCENE_SYSTEM.data)) cutsceneSystemNext();
}
void cutsceneSystemNext() {
if(CUTSCENE_SYSTEM.scene == NULL) return;
CUTSCENE_SYSTEM.currentItem++;
// Run off the end of the current scene? If it was spliced in via
// CUTSCENE_ITEM_TYPE_INSERT, pop back to whatever it was inserted into
// and resume right after the INSERT item - possibly chaining through
// several pops if that lands on the end of its own parent in turn (an
// insert as the very last item, or a nested insert-of-an-insert).
while(
CUTSCENE_SYSTEM.currentItem >= CUTSCENE_SYSTEM.scene->itemCount &&
CUTSCENE_SYSTEM.insertStackSize > 0
) {
cutsceneinsertframe_t frame =
CUTSCENE_SYSTEM.insertStack[--CUTSCENE_SYSTEM.insertStackSize];
CUTSCENE_SYSTEM.scene = frame.scene;
CUTSCENE_SYSTEM.currentItem = frame.currentItem + 1;
}
// End of the cutscene?
if(
CUTSCENE_SYSTEM.currentItem >= CUTSCENE_SYSTEM.scene->itemCount
) {
// Saved and cleared before firing so a callback that immediately
// starts another cutscene (or sets its own onComplete) isn't clobbered
// by this function's own cleanup running after it - same reentrancy
// hazard as uiFocusPop, see src/dusk/ui/focus/uifocus.c.
cutscenecallback_t onComplete = CUTSCENE_SYSTEM.onComplete;
void *userData = CUTSCENE_SYSTEM.userData;
CUTSCENE_SYSTEM.scene = NULL;
CUTSCENE_SYSTEM.currentItem = 0xFF;
CUTSCENE_SYSTEM.pause = CUTSCENE_PAUSE_NONE;
CUTSCENE_SYSTEM.entityInteract = NULL;
CUTSCENE_SYSTEM.entityInteracted = NULL;
CUTSCENE_SYSTEM.entityLastCreated = NULL;
CUTSCENE_SYSTEM.entityLastRef = NULL;
CUTSCENE_SYSTEM.areaLastCreated = CUTSCENE_AREA_LAST_CREATED;
CUTSCENE_SYSTEM.textMiniLastCreated = CUTSCENE_TEXT_MINI_LAST_CREATED;
CUTSCENE_SYSTEM.textCache[0] = '\0';
CUTSCENE_SYSTEM.onComplete = NULL;
CUTSCENE_SYSTEM.insertStackSize = 0;
if(onComplete != NULL) onComplete(userData);
return;
}
// Start item.
const cutsceneitem_t *item = cutsceneSystemGetCurrentItem();
memset(&CUTSCENE_SYSTEM.data, 0, sizeof(CUTSCENE_SYSTEM.data));
cutsceneItemStart(item, &CUTSCENE_SYSTEM.data);
}
void cutsceneSystemInsertCutscene(const cutscene_t *cutscene) {
assertNotNull(
CUTSCENE_SYSTEM.scene,
"cutsceneSystemInsertCutscene called with no cutscene running"
);
assertTrue(
CUTSCENE_SYSTEM.insertStackSize < CUTSCENE_INSERT_STACK_MAX,
"Cutscene INSERT nesting exceeds CUTSCENE_INSERT_STACK_MAX"
);
CUTSCENE_SYSTEM.insertStack[CUTSCENE_SYSTEM.insertStackSize++] =
(cutsceneinsertframe_t){
.scene = CUTSCENE_SYSTEM.scene,
.currentItem = CUTSCENE_SYSTEM.currentItem
};
CUTSCENE_SYSTEM.scene = cutscene;
CUTSCENE_SYSTEM.currentItem = 0xFF;// Set to 0xFF so Next wraps to 0.
cutsceneSystemNext();
}
void cutsceneSystemSetOnComplete(cutscenecallback_t onComplete) {
assertNotNull(
CUTSCENE_SYSTEM.scene,
@@ -236,33 +164,6 @@ void cutsceneSystemSetOnComplete(cutscenecallback_t onComplete) {
CUTSCENE_SYSTEM.onComplete = onComplete;
}
void cutsceneGoTo(const char_t *name) {
assertNotNull(
CUTSCENE_SYSTEM.scene, "cutsceneGoTo called with no cutscene running"
);
for(uint8_t i = 0; i < CUTSCENE_SYSTEM.scene->itemCount; i++) {
const cutsceneitem_t *item = &CUTSCENE_SYSTEM.scene->items[i];
if(
item->type == CUTSCENE_ITEM_TYPE_MARKER &&
stringEquals(item->marker.name, name)
) {
CUTSCENE_SYSTEM.currentItem = i;
memoryZero(&CUTSCENE_SYSTEM.data, sizeof(CUTSCENE_SYSTEM.data));
cutsceneItemStart(item, &CUTSCENE_SYSTEM.data);
return;
}
}
assertTrue(false, "cutsceneGoTo: no marker found with that name");
}
const cutsceneitem_t * cutsceneSystemGetCurrentItem() {
if(CUTSCENE_SYSTEM.scene == NULL) return NULL;
return &CUTSCENE_SYSTEM.scene->items[CUTSCENE_SYSTEM.currentItem];
}
entity_t * cutsceneSystemGetEntity(const uint8_t entityIndex) {
entity_t *entity;
@@ -335,20 +236,77 @@ void cutsceneSystemSetTextCache(const char_t *text) {
);
}
void cutsceneSystemDispose() {
CUTSCENE_SYSTEM.scene = NULL;
CUTSCENE_SYSTEM.currentItem = 0xFF;
CUTSCENE_SYSTEM.pause = CUTSCENE_PAUSE_NONE;
CUTSCENE_SYSTEM.entityInteract = NULL;
CUTSCENE_SYSTEM.entityInteracted = NULL;
CUTSCENE_SYSTEM.entityLastCreated = NULL;
CUTSCENE_SYSTEM.entityLastRef = NULL;
CUTSCENE_SYSTEM.areaLastCreated = CUTSCENE_AREA_LAST_CREATED;
CUTSCENE_SYSTEM.textMiniLastCreated = CUTSCENE_TEXT_MINI_LAST_CREATED;
CUTSCENE_SYSTEM.textCache[0] = '\0';
CUTSCENE_SYSTEM.onComplete = NULL;
CUTSCENE_SYSTEM.insertStackSize = 0;
void cutsceneSystemNext() {
if(CUTSCENE_SYSTEM.scene == NULL) return;
cutsceneLoadedSetDoc(NULL);
cutsceneInsertSetDoc(NULL);
CUTSCENE_SYSTEM.currentItem++;
// End of the cutscene? Note that a CUTSCENE_ITEM_TYPE_INSERT item's
// spliced-in items are physically part of this same array (see
// cutsceneSystemInsertCutscene), so there's no separate "parent scene"
// to fall back into here - itemCount already accounts for them.
if(
CUTSCENE_SYSTEM.currentItem >= CUTSCENE_SYSTEM.scene->itemCount
) {
// Saved and cleared before firing so a callback that immediately
// starts another cutscene (or sets its own onComplete) isn't clobbered
// by this function's own cleanup running after it - same reentrancy
// hazard as uiFocusPop, see src/dusk/ui/focus/uifocus.c.
cutscenecallback_t onComplete = CUTSCENE_SYSTEM.onComplete;
void *userData = CUTSCENE_SYSTEM.userData;
CUTSCENE_SYSTEM.scene = NULL;
CUTSCENE_SYSTEM.currentItem = 0xFF;
CUTSCENE_SYSTEM.pause = CUTSCENE_PAUSE_NONE;
CUTSCENE_SYSTEM.entityInteract = NULL;
CUTSCENE_SYSTEM.entityInteracted = NULL;
CUTSCENE_SYSTEM.entityLastCreated = NULL;
CUTSCENE_SYSTEM.entityLastRef = NULL;
CUTSCENE_SYSTEM.areaLastCreated = CUTSCENE_AREA_LAST_CREATED;
CUTSCENE_SYSTEM.textMiniLastCreated = CUTSCENE_TEXT_MINI_LAST_CREATED;
CUTSCENE_SYSTEM.textCache[0] = '\0';
CUTSCENE_SYSTEM.onComplete = NULL;
if(onComplete != NULL) onComplete(userData);
return;
}
// Start item.
const cutsceneitem_t *item = cutsceneSystemGetCurrentItem();
memset(&CUTSCENE_SYSTEM.data, 0, sizeof(CUTSCENE_SYSTEM.data));
cutsceneItemStart(item, &CUTSCENE_SYSTEM.data);
}
void cutsceneGoTo(const char_t *name) {
assertNotNull(
CUTSCENE_SYSTEM.scene, "cutsceneGoTo called with no cutscene running"
);
for(uint8_t i = 0; i < CUTSCENE_SYSTEM.scene->itemCount; i++) {
const cutsceneitem_t *item = &CUTSCENE_SYSTEM.scene->items[i];
if(
item->type == CUTSCENE_ITEM_TYPE_MARKER &&
stringEquals(item->marker.name, name)
) {
CUTSCENE_SYSTEM.currentItem = i;
memoryZero(&CUTSCENE_SYSTEM.data, sizeof(CUTSCENE_SYSTEM.data));
cutsceneItemStart(item, &CUTSCENE_SYSTEM.data);
return;
}
}
assertTrue(false, "cutsceneGoTo: no marker found with that name");
}
void cutsceneSystemUpdate() {
if(CUTSCENE_SYSTEM.scene == NULL) return;
const cutsceneitem_t *item = cutsceneSystemGetCurrentItem();
if(cutsceneItemUpdate(item, &CUTSCENE_SYSTEM.data)) cutsceneSystemNext();
}
const cutsceneitem_t * cutsceneSystemGetCurrentItem() {
if(CUTSCENE_SYSTEM.scene == NULL) return NULL;
return &CUTSCENE_SYSTEM.scene->items[CUTSCENE_SYSTEM.currentItem];
}
+78 -120
View File
@@ -1,6 +1,6 @@
/**
* Copyright (c) 2025 Dominic Masters
*
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
@@ -10,100 +10,26 @@
typedef struct entity_s entity_t;
#define CUTSCENE_ENTITY_INTERACT ((uint8_t)0xFE)
#define CUTSCENE_ENTITY_INTERACTED ((uint8_t)0xFD)
#define CUTSCENE_ENTITY_LAST_CREATED ((uint8_t)0xFC)
#define CUTSCENE_ENTITY_LAST_REF ((uint8_t)0xFB)
#define CUTSCENE_AREA_LAST_CREATED ((uint8_t)0xFF)
// Sentinels accepted in place of a literal index/ID by the cutsceneSystemGet*
// resolvers below.
#define CUTSCENE_ENTITY_INTERACT ((uint8_t)0xFE)
#define CUTSCENE_ENTITY_INTERACTED ((uint8_t)0xFD)
#define CUTSCENE_ENTITY_LAST_CREATED ((uint8_t)0xFC)
#define CUTSCENE_ENTITY_LAST_REF ((uint8_t)0xFB)
#define CUTSCENE_AREA_LAST_CREATED ((uint8_t)0xFF)
#define CUTSCENE_TEXT_MINI_LAST_CREATED ((uint8_t)0xFA)
// Size in bytes of cutscenesystem_t.userData, the running cutscene's
// scratch space.
#define CUTSCENE_SYSTEM_SIZE_MAX 8192
// Size of CUTSCENE_SYSTEM.textCache - keep >= UI_KEYBOARD_TEXT_MAX (see
// ui/dialog/keyboard/uikeyboard.h) so text entered via a
// CUTSCENE_ITEM_TYPE_KEYBOARD item is never truncated caching it here.
#define CUTSCENE_TEXT_CACHE_MAX 64
// Only one JSON-authored cutscene is ever loaded/playing at a time - a
// CUTSCENE item hands control off completely (see cutsceneCutsceneStart)
// and cutsceneSystemLoad/cutsceneCutsceneResolve release their asset lock
// the instant loading finishes - so there's no benefit to malloc'ing a
// fresh items buffer per load. Instead those functions parse directly
// into this fixed, static buffer via cutsceneParseDoc (see cutscene.h),
// once the raw JSON itself has been read+parsed via the generic
// ASSET_LOADER_TYPE_JSON asset loader. Bump this if a real cutscene ever
// needs more; C-authored cutscenes (used only by tests) don't use this
// at all - they're compiled directly into their own static const arrays.
#define CUTSCENE_LOADED_ITEMS_MAX 128
extern cutsceneitem_t CUTSCENE_LOADED_ITEMS[CUTSCENE_LOADED_ITEMS_MAX];
// The cutscene_t for whatever was last parsed into CUTSCENE_LOADED_ITEMS -
// .items always points at CUTSCENE_LOADED_ITEMS; itemCount/pause are
// overwritten by the loader on each load.
extern cutscene_t CUTSCENE_LOADED_SCENE;
/**
* Frees whatever yyjson_doc currently backs CUTSCENE_LOADED_ITEMS' string
* fields (marker/cutscene-reference names point directly into it - see
* cutsceneCutsceneRefLoad/cutsceneMarkerLoad) and takes ownership of doc
* instead. Called by cutsceneSystemLoad/cutsceneCutsceneResolve once a
* fresh parse into CUTSCENE_LOADED_ITEMS completes successfully, and from
* cutsceneSystemDispose to release the last one at shutdown.
*
* @param doc The new doc to take ownership of (NULL just frees/clears the
* current one).
*/
void cutsceneLoadedSetDoc(yyjson_doc *doc);
// A second, much smaller buffer dedicated to CUTSCENE_ITEM_TYPE_INSERT
// (see cutsceneSystemInsertCutscene) - kept entirely separate from
// CUTSCENE_LOADED_ITEMS so that a JSON-authored INSERT item resolving its
// target by name (via cutsceneInsertResolve) never overwrites the buffer
// the currently-running outer cutscene's own items live in. Sized small
// deliberately - an inserted snippet is meant to be a short splice, not a
// full cutscene.
// Scratch capacity for resolving a JSON-authored CUTSCENE_ITEM_TYPE_INSERT
// target - see cutsceneSystemInsertCutscene. Sized small deliberately -
// an inserted snippet is meant to be a short splice, not a full cutscene.
#define CUTSCENE_INSERT_ITEMS_MAX 16
extern cutsceneitem_t CUTSCENE_INSERT_ITEMS[CUTSCENE_INSERT_ITEMS_MAX];
// The cutscene_t for whatever was last parsed into CUTSCENE_INSERT_ITEMS -
// .items always points at CUTSCENE_INSERT_ITEMS. Only one JSON-authored
// INSERT target is ever resident at a time, same restriction as
// CUTSCENE_LOADED_SCENE - a JSON-authored INSERT whose target itself
// contains a JSON-authored INSERT would overwrite this out from under
// itself, so that isn't supported (compile-time CUTSCENE_INSERT nesting is
// unaffected, since it never touches asset loading).
extern cutscene_t CUTSCENE_INSERT_SCENE;
/**
* Same as cutsceneLoadedSetDoc, but for whatever yyjson_doc currently
* backs CUTSCENE_INSERT_ITEMS' string fields. Called by
* cutsceneInsertResolve once a fresh parse into CUTSCENE_INSERT_ITEMS
* completes successfully, and from cutsceneSystemDispose to release the
* last one at shutdown.
*
* @param doc The new doc to take ownership of (NULL just frees/clears the
* current one).
*/
void cutsceneInsertSetDoc(yyjson_doc *doc);
// Maximum nesting depth of CUTSCENE_ITEM_TYPE_INSERT items - how many
// "return to the item after this one" frames cutsceneSystemInsertCutscene
// may have pushed at once (see CUTSCENE_SYSTEM.insertStack).
#define CUTSCENE_INSERT_STACK_MAX 4
// One "return address" pushed by cutsceneSystemInsertCutscene - the scene
// and item index to resume at once the inserted cutscene's items run out.
typedef struct {
const cutscene_t *scene;
uint8_t currentItem;
} cutsceneinsertframe_t;
typedef struct {
const cutscene_t *scene;
cutscene_t *scene;
uint8_t currentItem;
cutscenepause_t pause;
entity_t *entityInteract;
@@ -119,7 +45,7 @@ typedef struct {
// may read or write it.
char_t textCache[CUTSCENE_TEXT_CACHE_MAX];
// Data (used by the current item).
// Runtime data for the current item.
cutsceneitemdata_t data;
// Custom user data for the running cutscene.
@@ -128,11 +54,12 @@ typedef struct {
// See cutsceneSystemSetOnComplete.
cutscenecallback_t onComplete;
// Return stack pushed by CUTSCENE_ITEM_TYPE_INSERT items - see
// cutsceneSystemInsertCutscene. Reset (insertStackSize = 0) whenever a
// cutscene freshly starts or the system is disposed.
cutsceneinsertframe_t insertStack[CUTSCENE_INSERT_STACK_MAX];
uint8_t insertStackSize;
// The fixed buffer/scene pair cutsceneSystemLoad/cutsceneCutsceneResolve
// parse a JSON-authored cutscene into - see CUTSCENE_LOADED_ITEMS_MAX
// above. loadedScene.items always points at loadedItems; loadedScene's
// itemCount/itemsMax/pause are overwritten on each load.
cutsceneitem_t loadedItems[CUTSCENE_LOADED_ITEMS_MAX];
cutscene_t loadedScene;
} cutscenesystem_t;
extern cutscenesystem_t CUTSCENE_SYSTEM;
@@ -142,12 +69,36 @@ extern cutscenesystem_t CUTSCENE_SYSTEM;
*/
void cutsceneSystemInit();
/**
* Disposes of the cutscene system, stopping any active cutscene.
*/
void cutsceneSystemDispose();
/**
* Resets CUTSCENE_SYSTEM to run cutscene from its first item, binding
* interact/interacted entities - shared setup used by
* cutsceneSystemStartCutsceneWith and
* cutsceneSystemStartCutsceneAndGoToMarker. Does not itself advance to the
* first item; callers do that afterward (via cutsceneSystemNext or
* cutsceneGoTo).
*
* @param cutscene Pointer to the cutscene to prepare.
* @param interact The entity that initiated the interaction (player), or
* NULL.
* @param interacted The entity being interacted with (NPC), or NULL.
*/
void cutsceneSystemPrepare(
cutscene_t *cutscene,
entity_t *interact,
entity_t *interacted
);
/**
* Start a cutscene with no bound entities.
*
* @param cutscene Pointer to the cutscene to start.
*/
void cutsceneSystemStartCutscene(const cutscene_t *cutscene);
void cutsceneSystemStartCutscene(cutscene_t *cutscene);
/**
* Start a cutscene with the two entities that triggered it.
@@ -157,7 +108,7 @@ void cutsceneSystemStartCutscene(const cutscene_t *cutscene);
* @param interacted The entity that was interacted with (NPC).
*/
void cutsceneSystemStartCutsceneWith(
const cutscene_t *cutscene,
cutscene_t *cutscene,
entity_t *interact,
entity_t *interacted
);
@@ -173,29 +124,29 @@ void cutsceneSystemStartCutsceneWith(
* @param marker Marker name to jump to, matched with stringEquals.
*/
void cutsceneSystemStartCutsceneAndGoToMarker(
const cutscene_t *cutscene,
cutscene_t *cutscene,
const char_t *marker
);
/**
* Splices cutscene's items into the running cutscene in place, resuming
* the current cutscene right after the CUTSCENE_ITEM_TYPE_INSERT item that
* called this once cutscene's items run out - unlike
* cutsceneSystemStartCutscene (a one-way jump that replaces the running
* cutscene outright). Pushes a return frame onto
* CUTSCENE_SYSTEM.insertStack; cutsceneSystemNext pops it once cutscene
* ends. Deliberately does not touch pause flags, interact entities,
* "last created" state, the text cache or userData - those all keep
* whatever the outer cutscene set, since this is meant to feel like
* pasting cutscene's items in place rather than starting an independent
* cutscene. Because of this, cutscene's own .pause is ignored.
* Asserts if no cutscene is currently running, or if inserts are already
* nested CUTSCENE_INSERT_STACK_MAX deep.
*
* Note: cutsceneRestart() resets the insert stack (via
* cutsceneSystemPrepare), so a RESTART item used from inside an inserted
* cutscene restarts just that inserted cutscene and does not return to
* whatever it was inserted into.
* Splices cutscene's items into the running cutscene (CUTSCENE_SYSTEM.scene)
* in place, right after the currently-executing item, via cutsceneAppendNext
* - unlike cutsceneSystemStartCutscene (a one-way jump that replaces the
* running cutscene outright), the inserted items simply become part of the
* running scene's own item array, so there's nothing separate to "return"
* to once they finish - whatever already followed the CUTSCENE_ITEM_TYPE_
* INSERT item continues normally, now shifted further down the same array.
* Then immediately advances into the first inserted item (see
* cutsceneSystemNext), so the splice takes effect within this same call.
* Deliberately does not touch pause flags, interact entities, "last
* created" state, the text cache or userData - those all keep whatever the
* outer cutscene set, since this is meant to feel like pasting cutscene's
* items in place rather than starting an independent cutscene. Because of
* this, cutscene's own .pause is ignored. Asserts if no cutscene is
* currently running, or if the running scene's own itemsMax has no room
* left for cutscene's items (see cutscene_t.itemsMax's doc comment - a
* cutscene that expects to have items inserted into it at runtime needs to
* be declared with spare capacity up front).
*
* @param cutscene The cutscene whose items to splice in.
*/
@@ -205,8 +156,8 @@ void cutsceneSystemInsertCutscene(const cutscene_t *cutscene);
* Loads and immediately starts a cutscene asset by file name, e.g.
* cutsceneSystemLoad("main_menu.jsonc") loads and starts
* assets/cutscenes/main_menu.jsonc, parsing it into
* CUTSCENE_LOADED_SCENE/ITEMS - the underlying JSON asset entry is
* unlocked and reaped immediately once that's done (see
* CUTSCENE_SYSTEM.loadedScene/loadedItems - the underlying JSON asset
* entry is unlocked and reaped immediately once that's done (see
* CUTSCENE_LOADED_ITEMS_MAX's doc comment for why), so it's always
* re-read+re-parsed fresh, never assumed still resident from a previous
* call. Opens the fatal error overlay (see uiFatalErrorOpen) instead of
@@ -217,6 +168,18 @@ void cutsceneSystemInsertCutscene(const cutscene_t *cutscene);
*/
void cutsceneSystemLoad(const char_t *file);
/**
* Frees whatever yyjson_doc was parsed into CUTSCENE_SYSTEM.loadedItems
* last and takes ownership of doc instead. Called by
* cutsceneSystemLoad/cutsceneCutsceneResolve once a fresh parse into
* loadedItems completes successfully, and from cutsceneSystemDispose to
* release the last one at shutdown.
*
* @param doc The new doc to take ownership of (NULL just frees/clears the
* current one).
*/
void cutsceneLoadedSetDoc(yyjson_doc *doc);
/**
* Restarts the currently running cutscene from its first item,
* preserving whatever interact/interacted entities triggered it.
@@ -322,8 +285,3 @@ void cutsceneSystemUpdate();
* @return Pointer to the current cutscene item.
*/
const cutsceneitem_t * cutsceneSystemGetCurrentItem();
/**
* Disposes of the cutscene system, stopping any active cutscene.
*/
void cutsceneSystemDispose();
@@ -6,6 +6,7 @@
*/
#include "rpg/cutscene/item/cutsceneitem.h"
#include "rpg/cutscene/item/json/cutscenejsonfield.h"
void cutsceneMarkerStart(
const cutsceneitem_t *item,
@@ -24,11 +25,11 @@ errorret_t cutsceneMarkerLoad(
yyjson_val *itemObj,
cutsceneitem_t *item
) {
const char_t *name = yyjson_get_str(yyjson_obj_get(itemObj, "name"));
if(name == NULL) {
errorThrow("Cutscene item (MARKER): missing 'name'");
if(!cutsceneJsonCopyString(
itemObj, "name", NULL, item->marker.name, CUTSCENE_MARKER_NAME_MAX
)) {
errorThrow("Cutscene item (MARKER): missing/too-long 'name'");
}
item->marker.name = name;
errorOk();
}
@@ -8,8 +8,13 @@
#pragma once
#include "rpg/cutscene/item/cutsceneitembase.h"
// Also the max length of a marker name referenced (not declared) by other
// item types - see cutscenesavedevicecheck_t/cutscenesaveloadallslots_t/
// cutscenemodaloptionsmarkers_t.
#define CUTSCENE_MARKER_NAME_MAX 32
typedef struct {
const char_t *name;
char_t name[CUTSCENE_MARKER_NAME_MAX];
} cutscenemarker_t;
/**
+27 -31
View File
@@ -377,8 +377,8 @@ void cutsceneCutsceneStart(
const cutsceneitem_t *item,
cutsceneitemdata_t *data
) {
const cutscene_t *cutscene = item->cutsceneRef.cutscene;
if(cutscene == NULL && item->cutsceneRef.name != NULL) {
cutscene_t *cutscene = item->cutsceneRef.cutscene;
if(cutscene == NULL && item->cutsceneRef.name[0] != '\0') {
// Not cached back into item - with only one shared, fixed load buffer
// (see cutscenesystem.h's CUTSCENE_LOADED_ITEMS_MAX doc comment),
// every JSON-authored reference resolves to the same fixed address
@@ -397,7 +397,7 @@ bool_t cutsceneCutsceneUpdate(
return false;
}
const cutscene_t * cutsceneCutsceneResolve(const char_t *name) {
cutscene_t * cutsceneCutsceneResolve(const char_t *name) {
char_t path[ASSET_FILE_NAME_MAX];
stringFormat(path, sizeof(path), "cutscenes/%s.jsonc", name);
@@ -411,8 +411,7 @@ const cutscene_t * cutsceneCutsceneResolve(const char_t *name) {
}
// Steal the parsed doc before unlocking - assetJsonDispose would
// otherwise free it out from under CUTSCENE_LOADED_ITEMS' name pointers
// the moment this entry gets reaped.
// otherwise free it out before it's been parsed below.
yyjson_doc *doc = entry->data.json;
entry->data.json = NULL;
assetUnlockEntry(entry);
@@ -421,12 +420,12 @@ const cutscene_t * cutsceneCutsceneResolve(const char_t *name) {
// trigger a reap - assetRequireLoaded treats an already-LOADED entry as
// an instant cache hit with no reparsing, so a stale leftover entry for
// this same path would otherwise make a later resolve of it wrongly
// skip loading, still returning CUTSCENE_LOADED_SCENE even after some
// other cutscene has since overwritten it.
// skip loading, still returning CUTSCENE_SYSTEM.loadedScene even after
// some other cutscene has since overwritten it.
assetReapUnused();
errorret_t parseResult = cutsceneParseDoc(
doc, &CUTSCENE_LOADED_SCENE, CUTSCENE_LOADED_ITEMS,
doc, &CUTSCENE_SYSTEM.loadedScene, CUTSCENE_SYSTEM.loadedItems,
CUTSCENE_LOADED_ITEMS_MAX
);
if(errorIsNotOk(parseResult)) {
@@ -437,7 +436,7 @@ const cutscene_t * cutsceneCutsceneResolve(const char_t *name) {
}
cutsceneLoadedSetDoc(doc);
return &CUTSCENE_LOADED_SCENE;
return &CUTSCENE_SYSTEM.loadedScene;
}
errorret_t cutsceneCutsceneLoad(
@@ -466,7 +465,7 @@ errorret_t cutsceneCutsceneRefLoad(
}
outRef->cutscene = NULL;
outRef->name = name;
stringCopy(outRef->name, name, CUTSCENE_CUTSCENE_REF_NAME_MAX);
errorOk();
}
@@ -475,16 +474,11 @@ void cutsceneInsertStart(
const cutsceneitem_t *item,
cutsceneitemdata_t *data
) {
const cutscene_t *cutscene = item->insert.cutscene;
if(cutscene == NULL && item->insert.name != NULL) {
// Not cached back into item - same reasoning as cutsceneCutsceneStart,
// but resolved via cutsceneInsertResolve (the dedicated INSERT buffer)
// rather than cutsceneCutsceneResolve, since item itself may live
// inside the currently-running outer cutscene's own CUTSCENE_LOADED_*
// buffer and must not be clobbered by this resolve.
cutscene = cutsceneInsertResolve(item->insert.name);
if(item->insert.cutscene != NULL) {
cutsceneSystemInsertCutscene(item->insert.cutscene);
} else if(item->insert.name[0] != '\0') {
cutsceneInsertResolveAndSplice(item->insert.name);
}
if(cutscene != NULL) cutsceneSystemInsertCutscene(cutscene);
}
bool_t cutsceneInsertUpdate(
@@ -494,7 +488,7 @@ bool_t cutsceneInsertUpdate(
return false;
}
const cutscene_t * cutsceneInsertResolve(const char_t *name) {
void cutsceneInsertResolveAndSplice(const char_t *name) {
char_t path[ASSET_FILE_NAME_MAX];
stringFormat(path, sizeof(path), "cutscenes/%s.jsonc", name);
@@ -504,12 +498,11 @@ const cutscene_t * cutsceneInsertResolve(const char_t *name) {
assetUnlockEntry(entry);
errorCatch(errorPrint(result));
uiFatalErrorOpen(NULL);
return NULL;
return;
}
// Steal the parsed doc before unlocking - assetJsonDispose would
// otherwise free it out from under CUTSCENE_INSERT_ITEMS' name pointers
// the moment this entry gets reaped.
// otherwise free it out before it's been parsed below.
yyjson_doc *doc = entry->data.json;
entry->data.json = NULL;
assetUnlockEntry(entry);
@@ -518,19 +511,22 @@ const cutscene_t * cutsceneInsertResolve(const char_t *name) {
// never wrongly skips reloading it.
assetReapUnused();
errorret_t parseResult = cutsceneParseDoc(
doc, &CUTSCENE_INSERT_SCENE, CUTSCENE_INSERT_ITEMS,
CUTSCENE_INSERT_ITEMS_MAX
);
// Local scratch, not a shared buffer - this target is spliced straight
// into the running scene below and never referenced again afterward, so
// it only needs to live for the rest of this call (see this function's
// doc comment for why the doc can be freed immediately too).
cutsceneitem_t items[CUTSCENE_INSERT_ITEMS_MAX];
cutscene_t scene = { .items = items };
errorret_t parseResult =
cutsceneParseDoc(doc, &scene, items, CUTSCENE_INSERT_ITEMS_MAX);
yyjson_doc_free(doc);
if(errorIsNotOk(parseResult)) {
yyjson_doc_free(doc);
errorCatch(errorPrint(parseResult));
uiFatalErrorOpen(NULL);
return NULL;
return;
}
cutsceneInsertSetDoc(doc);
return &CUTSCENE_INSERT_SCENE;
cutsceneSystemInsertCutscene(&scene);
}
errorret_t cutsceneInsertLoad(
+60 -50
View File
@@ -8,6 +8,7 @@
#pragma once
#include "error/error.h"
#include "yyjson.h"
#include "asset/assetfile.h"
#include "cutsceneitembase.h"
#include "cutscenecallback.h"
#include "cutsceneprint.h"
@@ -57,28 +58,37 @@
typedef struct cutscene_s cutscene_t;
// A CUTSCENE (or INSERT) item's target. C-authored cutscenes (the
// CUTSCENE_CUTSCENE/CUTSCENE_INSERT macros in cutscene.h) resolve cutscene
// at compile time and leave name NULL - safe to hold onto indefinitely,
// since C-authored cutscenes are compiled directly into their own static
// const arrays, never a shared load buffer. JSON-authored cutscenes
// (cutsceneCutsceneLoad/cutsceneInsertLoad) do the opposite - only name is
// set at parse time, since resolving/locking the referenced asset there
// would eagerly load every cutscene a cutscene merely references, even
// ones never played. cutsceneCutsceneStart/cutsceneInsertStart resolve
// name to cutscene the first time this item actually runs (via
// cutsceneCutsceneResolve/cutsceneInsertResolve respectively) but do NOT
// cache the result back into this field - with only one shared, fixed
// buffer per destination (see cutscenesystem.h), every JSON-authored
// Max length of a CUTSCENE/INSERT item's "name" field - bounded by what
// fits in the "cutscenes/<name>.jsonc" path built from it (see
// cutsceneCutsceneRefLoad), so this is never reached in practice.
#define CUTSCENE_CUTSCENE_REF_NAME_MAX ASSET_FILE_NAME_MAX
// A CUTSCENE (or INSERT) item's target. A C-authored cutscene_t* set
// directly (e.g. by a test) leaves name empty ("") - safe to hold onto
// indefinitely, since C-authored cutscenes are compiled directly into
// their own static arrays, never a shared load buffer. JSON-authored
// cutscenes (cutsceneCutsceneLoad/cutsceneInsertLoad) do the opposite -
// only name is set at parse time (copied by value out of the JSON doc,
// so it stays valid regardless of the doc's own lifetime), since
// resolving/locking the referenced asset there would eagerly load every
// cutscene a cutscene merely references, even ones never played.
// cutsceneCutsceneStart resolves name to cutscene the first time this
// item actually runs (via cutsceneCutsceneResolve) but does NOT cache the
// result back into this field - with only one shared, fixed buffer (see
// CUTSCENE_LOADED_ITEMS_MAX in cutscenesystem.h), every JSON-authored
// reference resolves to the same fixed address every time anyway, and
// item itself may live inside that very buffer, so caching a pointer into
// it here would go stale (or, worse, get clobbered mid-resolve) the
// moment a later load overwrites it. Shared by both CUTSCENE
// (item->cutsceneRef) and INSERT (item->insert), since both need exactly
// this "resolved pointer, or a name to lazily resolve" shape.
// item itself may live inside that very buffer, so caching a pointer
// into it here would go stale (or, worse, get clobbered mid-resolve) the
// moment a later load overwrites it. cutsceneInsertStart instead resolves
// AND splices name in one step (cutsceneInsertResolveAndSplice) using a
// local scratch buffer that's gone by the time this item's Start
// returns, so there's nothing to cache back for INSERT even in principle.
// Shared by both CUTSCENE (item->cutsceneRef) and INSERT (item->insert),
// since both need exactly this "resolved pointer, or a name to lazily
// resolve" shape.
typedef struct {
const cutscene_t *cutscene;
const char_t *name;
cutscene_t *cutscene;
char_t name[CUTSCENE_CUTSCENE_REF_NAME_MAX];
} cutscenecutsceneref_t;
struct cutsceneitem_s {
@@ -215,20 +225,21 @@ void cutsceneCutsceneStart(
);
/**
* Resolves a JSON-authored CUTSCENE item's referenced cutscene by name,
* locking (forever - never unlocked, matching cutsceneCutsceneLoad's old
* convention) and blocking on assets/cutscenes/<name>.jsonc. Called
* lazily from cutsceneCutsceneStart the first time the item actually
* runs, not at parse time, so a cutscene referencing many others doesn't
* load every one of them up front. On failure, opens the fatal error
* overlay (matching cutsceneSystemLoad's runtime-failure handling) and
* returns NULL.
* Resolves a JSON-authored CUTSCENE item's referenced cutscene by name:
* locks+loads assets/cutscenes/<name>.jsonc, parses it into
* CUTSCENE_SYSTEM.loadedItems/loadedScene, then unlocks and reaps the asset entry
* immediately (see CUTSCENE_LOADED_ITEMS_MAX's doc comment for why).
* Called lazily from cutsceneCutsceneStart the first time the item
* actually runs, not at parse time, so a cutscene referencing many others
* doesn't load every one of them up front. On failure, opens the fatal
* error overlay (matching cutsceneSystemLoad's runtime-failure handling)
* and returns NULL.
*
* @param name Bare cutscene name (without "cutscenes/" prefix or
* ".jsonc" suffix).
* @return The resolved cutscene, or NULL on failure.
*/
const cutscene_t * cutsceneCutsceneResolve(const char_t *name);
cutscene_t * cutsceneCutsceneResolve(const char_t *name);
/**
* Updates a nested-cutscene item. By the time this would run, control
@@ -283,17 +294,13 @@ errorret_t cutsceneCutsceneRefLoad(
/**
* Starts an INSERT item, splicing the referenced cutscene's items into the
* running cutscene in place - unlike CUTSCENE (a one-way jump that replaces
* the running cutscene outright), control returns to the item right after
* this one once the inserted items run out. If item->insert.cutscene hasn't
* been resolved yet (a JSON-authored reference, only known by name so
* far), resolves it first via cutsceneInsertResolve - not
* cutsceneCutsceneResolve, so the referenced cutscene lands in the
* dedicated CUTSCENE_INSERT_ITEMS buffer rather than overwriting
* CUTSCENE_LOADED_ITEMS out from under whatever outer cutscene is
* currently running (see cutscenesystem.h's CUTSCENE_INSERT_ITEMS_MAX doc
* comment). Not cached back into item, same reasoning as
* cutsceneCutsceneStart.
* running cutscene in place via cutsceneSystemInsertCutscene - unlike
* CUTSCENE (a one-way jump that replaces the running cutscene outright),
* whatever already followed this item continues normally once the
* inserted items run out, just shifted further down the same array. If
* item->insert.cutscene is set (a compile-time target), splices it
* directly; otherwise (a JSON-authored reference, only known by name so
* far) resolves and splices it via cutsceneInsertResolveAndSplice.
*
* @param item The cutscene item.
* @param data Runtime data storage.
@@ -304,20 +311,23 @@ void cutsceneInsertStart(
);
/**
* Resolves a JSON-authored INSERT item's referenced cutscene by name -
* locks+loads assets/cutscenes/<name>.jsonc (as a plain
* ASSET_LOADER_TYPE_JSON asset, same path/key convention as
* cutsceneCutsceneResolve - which destination buffer it lands in is
* decided purely by which resolve function is called, not stored on the
* asset entry itself), then parses it into CUTSCENE_INSERT_ITEMS rather
* than CUTSCENE_LOADED_ITEMS. On failure, opens the fatal error overlay
* and returns NULL.
* Resolves a JSON-authored INSERT item's referenced cutscene by name and
* splices it into the running cutscene (CUTSCENE_SYSTEM.scene): locks+
* loads assets/cutscenes/<name>.jsonc as a plain ASSET_LOADER_TYPE_JSON
* asset, parses it into a small scratch buffer local to this call
* (capacity CUTSCENE_INSERT_ITEMS_MAX), then immediately frees the parsed
* doc - unlike cutsceneCutsceneResolve's CUTSCENE_SYSTEM.loadedScene, nothing
* needs to persist past this call: cutsceneSystemInsertCutscene copies the
* scratch items into the running scene's own array, and every cutscene
* item field that could reference the doc's memory owns its string data
* by value (see cutscenecutsceneref_t/cutscenemarker_t's doc comments), so
* the doc is disposable the moment parsing finishes. On failure, opens the
* fatal error overlay, matching cutsceneCutsceneResolve.
*
* @param name Bare cutscene name (without "cutscenes/" prefix or
* ".jsonc" suffix).
* @return The resolved cutscene, or NULL on failure.
*/
const cutscene_t * cutsceneInsertResolve(const char_t *name);
void cutsceneInsertResolveAndSplice(const char_t *name);
/**
* Updates an INSERT item. By the time this would run, control has already
@@ -7,6 +7,7 @@
#include "rpg/cutscene/item/cutsceneitem.h"
#include "rpg/cutscene/cutscenesystem.h"
#include "rpg/cutscene/item/json/cutscenejsonfield.h"
#include "save/save.h"
void cutsceneSaveDeviceCheckCallback(savedevice_t *device, void *user) {
@@ -36,18 +37,21 @@ errorret_t cutsceneSaveDeviceCheckLoad(
yyjson_val *itemObj,
cutsceneitem_t *item
) {
const char_t *successMarker =
yyjson_get_str(yyjson_obj_get(itemObj, "successMarker"));
const char_t *failureMarker =
yyjson_get_str(yyjson_obj_get(itemObj, "failureMarker"));
if(successMarker == NULL || failureMarker == NULL) {
if(
!cutsceneJsonCopyString(
itemObj, "successMarker", NULL, item->saveDeviceCheck.successMarker,
CUTSCENE_MARKER_NAME_MAX
) ||
!cutsceneJsonCopyString(
itemObj, "failureMarker", NULL, item->saveDeviceCheck.failureMarker,
CUTSCENE_MARKER_NAME_MAX
)
) {
errorThrow(
"Cutscene item (SAVE_DEVICE_CHECK): needs 'successMarker'/"
"'failureMarker'"
);
}
item->saveDeviceCheck.successMarker = successMarker;
item->saveDeviceCheck.failureMarker = failureMarker;
errorOk();
}
@@ -7,10 +7,11 @@
#pragma once
#include "rpg/cutscene/item/cutsceneitembase.h"
#include "rpg/cutscene/item/control/cutscenemarker.h"
typedef struct {
const char_t *successMarker;
const char_t *failureMarker;
char_t successMarker[CUTSCENE_MARKER_NAME_MAX];
char_t failureMarker[CUTSCENE_MARKER_NAME_MAX];
} cutscenesavedevicecheck_t;
/**
@@ -7,6 +7,7 @@
#include "rpg/cutscene/item/cutsceneitem.h"
#include "rpg/cutscene/cutscenesystem.h"
#include "rpg/cutscene/item/json/cutscenejsonfield.h"
#include "save/save.h"
void cutsceneSaveLoadAllSlotsStart(
@@ -34,18 +35,21 @@ errorret_t cutsceneSaveLoadAllSlotsLoad(
yyjson_val *itemObj,
cutsceneitem_t *item
) {
const char_t *successMarker =
yyjson_get_str(yyjson_obj_get(itemObj, "successMarker"));
const char_t *failureMarker =
yyjson_get_str(yyjson_obj_get(itemObj, "failureMarker"));
if(successMarker == NULL || failureMarker == NULL) {
if(
!cutsceneJsonCopyString(
itemObj, "successMarker", NULL, item->saveLoadAllSlots.successMarker,
CUTSCENE_MARKER_NAME_MAX
) ||
!cutsceneJsonCopyString(
itemObj, "failureMarker", NULL, item->saveLoadAllSlots.failureMarker,
CUTSCENE_MARKER_NAME_MAX
)
) {
errorThrow(
"Cutscene item (SAVE_LOAD_ALL_SLOTS): needs 'successMarker'/"
"'failureMarker'"
);
}
item->saveLoadAllSlots.successMarker = successMarker;
item->saveLoadAllSlots.failureMarker = failureMarker;
errorOk();
}
@@ -7,10 +7,11 @@
#pragma once
#include "rpg/cutscene/item/cutsceneitembase.h"
#include "rpg/cutscene/item/control/cutscenemarker.h"
typedef struct {
const char_t *successMarker;
const char_t *failureMarker;
char_t successMarker[CUTSCENE_MARKER_NAME_MAX];
char_t failureMarker[CUTSCENE_MARKER_NAME_MAX];
} cutscenesaveloadallslots_t;
/**
@@ -32,10 +32,18 @@ void cutsceneModalOptionsMarkersStart(
) {
const cutscenemodaloptionsmarkers_t *options = &item->modalOptionsMarkers;
// uiModalOpen wants an array of string pointers; options->options is a
// 2D array of owned char buffers (see the struct's doc comment), so
// build the pointer array it expects here.
const char_t *optionPtrs[CUTSCENE_MODAL_OPTIONS_MARKERS_MAX];
for(uint8_t i = 0; i < options->optionCount; i++) {
optionPtrs[i] = options->options[i];
}
uiModalOpen(
options->title,
options->message,
options->options,
optionPtrs,
options->optionCount,
cutsceneModalOptionsMarkersCallback,
NULL,
@@ -82,17 +90,21 @@ errorret_t cutsceneModalOptionsMarkersLoad(
yyjson_val *optionVal;
size_t o = 0;
while((optionVal = yyjson_arr_iter_next(&iter)) != NULL) {
const char_t *text = yyjson_get_str(yyjson_obj_get(optionVal, "text"));
const char_t *marker =
yyjson_get_str(yyjson_obj_get(optionVal, "marker"));
if(text == NULL || marker == NULL) {
if(
!cutsceneJsonCopyString(
optionVal, "text", NULL, item->modalOptionsMarkers.options[o],
UI_MODAL_OPTION_TEXT_MAX
) ||
!cutsceneJsonCopyString(
optionVal, "marker", NULL, item->modalOptionsMarkers.markers[o],
CUTSCENE_MARKER_NAME_MAX
)
) {
errorThrow(
"Cutscene item (MODAL_OPTIONS_MARKERS): options[%u] needs 'text' "
"and 'marker'", (uint32_t)o
"Cutscene item (MODAL_OPTIONS_MARKERS): options[%u] needs "
"'text'/'marker'", (uint32_t)o
);
}
item->modalOptionsMarkers.options[o] = text;
item->modalOptionsMarkers.markers[o] = marker;
o++;
}
@@ -8,9 +8,11 @@
#pragma once
#include "rpg/cutscene/item/cutsceneitembase.h"
#include "rpg/cutscene/item/ui/cutscenemodal.h"
#include "rpg/cutscene/item/control/cutscenemarker.h"
#include "ui/widget/uimodal.h"
// Backs CUTSCENE_MODAL_OPTIONS_ONE and CUTSCENE_MODAL_OPTIONS_TWO, so two
// is the most either macro ever needs.
// Two options is the most either JSON MODAL_OPTIONS_MARKERS item ever
// needs.
#define CUTSCENE_MODAL_OPTIONS_MARKERS_MAX 2
typedef struct {
@@ -20,9 +22,11 @@ typedef struct {
char_t message[CUTSCENE_MODAL_MESSAGE_MAX_CHARS];
// Each option's display text/locale message ID, and the marker to
// cutsceneGoTo when that option is selected.
const char_t *options[CUTSCENE_MODAL_OPTIONS_MARKERS_MAX];
const char_t *markers[CUTSCENE_MODAL_OPTIONS_MARKERS_MAX];
// cutsceneGoTo when that option is selected. Copied by value at parse
// time (see cutsceneModalOptionsMarkersLoad), so they stay valid
// regardless of the backing JSON doc's lifetime.
char_t options[CUTSCENE_MODAL_OPTIONS_MARKERS_MAX][UI_MODAL_OPTION_TEXT_MAX];
char_t markers[CUTSCENE_MODAL_OPTIONS_MARKERS_MAX][CUTSCENE_MARKER_NAME_MAX];
uint8_t optionCount;
} cutscenemodaloptionsmarkers_t;
@@ -28,7 +28,7 @@ void entityInteractWith(entity_t *player, entity_t *target) {
break;
case ENTITY_INTERACT_CUTSCENE_NAME: {
const cutscene_t *cutscene =
cutscene_t *cutscene =
cutsceneCutsceneResolve(target->interact.data.cutsceneName);
if(cutscene != NULL) {
cutsceneSystemStartCutsceneWith(cutscene, player, target);
@@ -31,9 +31,10 @@ typedef enum {
typedef union {
// A compile-time cutscene_t* (a C-authored, test-only cutscene) - safe
// to hold onto indefinitely, since it's compiled directly into its own
// static const arrays, never the shared CUTSCENE_LOADED_ITEMS buffer
// (see cutscenesystem.h).
const cutscene_t *cutscene;
// static arrays, never the shared CUTSCENE_SYSTEM.loadedItems buffer (see
// cutscenesystem.h). Non-const since cutsceneSystemStartCutsceneWith may
// splice CUTSCENE_ITEM_TYPE_INSERT items into it at runtime.
cutscene_t *cutscene;
// A bare cutscene name (as passed to cutsceneCutsceneResolve), resolved
// lazily at interact time rather than once up front - only one
// JSON-authored cutscene is ever resident at a time (see
+4 -4
View File
@@ -27,7 +27,7 @@
uimainmenu_t UI_MAIN_MENU;
const cutscene_t *uiMainMenuLoadCutscene(void) {
cutscene_t *uiMainMenuLoadCutscene(void) {
// Delegates entirely to cutsceneCutsceneResolve rather than keeping our
// own locked-forever entry, since only one JSON-authored cutscene is
// ever resident at a time (see CUTSCENE_LOADED_ITEMS_MAX's doc comment
@@ -73,7 +73,7 @@ void uiMainMenuOpenSelectSave(void *userData) {
void uiMainMenuStartGame(void) {
if(CUTSCENE_SYSTEM.scene == NULL) {
const cutscene_t *cutscene = uiMainMenuLoadCutscene();
cutscene_t *cutscene = uiMainMenuLoadCutscene();
if(cutscene == NULL) return;
cutsceneSystemStartCutsceneAndGoToMarker(cutscene, "NEW_GAME");
} else {
@@ -85,7 +85,7 @@ void uiMainMenuStartGame(void) {
void uiMainMenuOptions(void) {
if(CUTSCENE_SYSTEM.scene == NULL) {
const cutscene_t *cutscene = uiMainMenuLoadCutscene();
cutscene_t *cutscene = uiMainMenuLoadCutscene();
if(cutscene == NULL) return;
cutsceneSystemStartCutsceneAndGoToMarker(cutscene, "OPTIONS");
} else {
@@ -95,7 +95,7 @@ void uiMainMenuOptions(void) {
void uiMainMenuQuit(void) {
if(CUTSCENE_SYSTEM.scene == NULL) {
const cutscene_t *cutscene = uiMainMenuLoadCutscene();
cutscene_t *cutscene = uiMainMenuLoadCutscene();
if(cutscene == NULL) return;
cutsceneSystemStartCutsceneAndGoToMarker(cutscene, "QUIT");
} else {
+1 -1
View File
@@ -50,7 +50,7 @@ void uiMainMenuOpen(void);
* @return The main menu cutscene, or NULL on failure (already reported via
* uiFatalErrorOpen).
*/
const cutscene_t *uiMainMenuLoadCutscene(void);
cutscene_t *uiMainMenuLoadCutscene(void);
/**
* Starts the "Start Game" flow: jumps the already-running main menu