From 5910110b9559bc893fca7d22b3d9af4887027322 Mon Sep 17 00:00:00 2001 From: Dominic Masters Date: Thu, 17 Sep 2026 22:26:36 -0500 Subject: [PATCH] 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 --- assets/battle/moves.jsonc | 7 - src/dusk/rpg/cutscene/cutscene.c | 41 ++- src/dusk/rpg/cutscene/cutscene.h | 85 ++++-- src/dusk/rpg/cutscene/cutscenesystem.c | 276 ++++++++---------- src/dusk/rpg/cutscene/cutscenesystem.h | 198 +++++-------- .../cutscene/item/control/cutscenemarker.c | 9 +- .../cutscene/item/control/cutscenemarker.h | 7 +- src/dusk/rpg/cutscene/item/cutsceneitem.c | 58 ++-- src/dusk/rpg/cutscene/item/cutsceneitem.h | 110 +++---- .../item/save/cutscenesavedevicecheck.c | 18 +- .../item/save/cutscenesavedevicecheck.h | 5 +- .../item/save/cutscenesaveloadallslots.c | 18 +- .../item/save/cutscenesaveloadallslots.h | 5 +- .../item/ui/cutscenemodaloptionsmarkers.c | 30 +- .../item/ui/cutscenemodaloptionsmarkers.h | 14 +- src/dusk/rpg/entity/interact/entityinteract.c | 2 +- src/dusk/rpg/entity/interact/entityinteract.h | 7 +- src/dusk/ui/screen/mainmenu/uimainmenu.c | 8 +- src/dusk/ui/screen/mainmenu/uimainmenu.h | 2 +- test/rpg/battle/test_battle.c | 3 +- test/rpg/cutscene/test_cutscenesystem.c | 224 +++++++------- test/rpg/entity/test_entityinteract.c | 3 +- 22 files changed, 548 insertions(+), 582 deletions(-) diff --git a/assets/battle/moves.jsonc b/assets/battle/moves.jsonc index 9ddc4313..83d6120b 100644 --- a/assets/battle/moves.jsonc +++ b/assets/battle/moves.jsonc @@ -1,17 +1,10 @@ { - // Keyed by move id. "ability" matches a BATTLE_ABILITY_* enum name - // (see src/dusk/rpg/battle/battleability.h) - there's no JSON loader - // consuming this file yet, so field names/shape here are a starting - // point, not a contract. "moves": { "fire": { "ability": "FIRE", "power": 12, "mpCost": 4, "target": "SINGLE_ENEMY", - // Optional - name of a cutscene (see assets/cutscenes/) to play - // when this move is used, resolved lazily like a CUTSCENE item's - // "name" field. Omit for a move with no special presentation. "cutscene": "battle/fire" } } diff --git a/src/dusk/rpg/cutscene/cutscene.c b/src/dusk/rpg/cutscene/cutscene.c index 7e562b10..8ff7715b 100644 --- a/src/dusk/rpg/cutscene/cutscene.c +++ b/src/dusk/rpg/cutscene/cutscene.c @@ -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); +} diff --git a/src/dusk/rpg/cutscene/cutscene.h b/src/dusk/rpg/cutscene/cutscene.h index f2b69da5..2da53b0c 100644 --- a/src/dusk/rpg/cutscene/cutscene.h +++ b/src/dusk/rpg/cutscene/cutscene.h @@ -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 +); diff --git a/src/dusk/rpg/cutscene/cutscenesystem.c b/src/dusk/rpg/cutscene/cutscenesystem.c index 928046ec..d1bd8f22 100644 --- a/src/dusk/rpg/cutscene/cutscenesystem.c +++ b/src/dusk/rpg/cutscene/cutscenesystem.c @@ -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]; } diff --git a/src/dusk/rpg/cutscene/cutscenesystem.h b/src/dusk/rpg/cutscene/cutscenesystem.h index d80d1616..78ca1518 100644 --- a/src/dusk/rpg/cutscene/cutscenesystem.h +++ b/src/dusk/rpg/cutscene/cutscenesystem.h @@ -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(); \ No newline at end of file diff --git a/src/dusk/rpg/cutscene/item/control/cutscenemarker.c b/src/dusk/rpg/cutscene/item/control/cutscenemarker.c index 1e25da51..0c210f5c 100644 --- a/src/dusk/rpg/cutscene/item/control/cutscenemarker.c +++ b/src/dusk/rpg/cutscene/item/control/cutscenemarker.c @@ -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(); } diff --git a/src/dusk/rpg/cutscene/item/control/cutscenemarker.h b/src/dusk/rpg/cutscene/item/control/cutscenemarker.h index a2bf1bc6..8da1dc9b 100644 --- a/src/dusk/rpg/cutscene/item/control/cutscenemarker.h +++ b/src/dusk/rpg/cutscene/item/control/cutscenemarker.h @@ -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; /** diff --git a/src/dusk/rpg/cutscene/item/cutsceneitem.c b/src/dusk/rpg/cutscene/item/cutsceneitem.c index 4085cce6..feb93d13 100644 --- a/src/dusk/rpg/cutscene/item/cutsceneitem.c +++ b/src/dusk/rpg/cutscene/item/cutsceneitem.c @@ -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( diff --git a/src/dusk/rpg/cutscene/item/cutsceneitem.h b/src/dusk/rpg/cutscene/item/cutsceneitem.h index d3d338f6..d0a36cdd 100644 --- a/src/dusk/rpg/cutscene/item/cutsceneitem.h +++ b/src/dusk/rpg/cutscene/item/cutsceneitem.h @@ -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/.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/.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/.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/.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/.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 diff --git a/src/dusk/rpg/cutscene/item/save/cutscenesavedevicecheck.c b/src/dusk/rpg/cutscene/item/save/cutscenesavedevicecheck.c index 374710d9..9d6e4813 100644 --- a/src/dusk/rpg/cutscene/item/save/cutscenesavedevicecheck.c +++ b/src/dusk/rpg/cutscene/item/save/cutscenesavedevicecheck.c @@ -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(); } diff --git a/src/dusk/rpg/cutscene/item/save/cutscenesavedevicecheck.h b/src/dusk/rpg/cutscene/item/save/cutscenesavedevicecheck.h index cf832814..7bd0f1cd 100644 --- a/src/dusk/rpg/cutscene/item/save/cutscenesavedevicecheck.h +++ b/src/dusk/rpg/cutscene/item/save/cutscenesavedevicecheck.h @@ -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; /** diff --git a/src/dusk/rpg/cutscene/item/save/cutscenesaveloadallslots.c b/src/dusk/rpg/cutscene/item/save/cutscenesaveloadallslots.c index 483e8073..06745331 100644 --- a/src/dusk/rpg/cutscene/item/save/cutscenesaveloadallslots.c +++ b/src/dusk/rpg/cutscene/item/save/cutscenesaveloadallslots.c @@ -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(); } diff --git a/src/dusk/rpg/cutscene/item/save/cutscenesaveloadallslots.h b/src/dusk/rpg/cutscene/item/save/cutscenesaveloadallslots.h index d954c23c..fc237b47 100644 --- a/src/dusk/rpg/cutscene/item/save/cutscenesaveloadallslots.h +++ b/src/dusk/rpg/cutscene/item/save/cutscenesaveloadallslots.h @@ -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; /** diff --git a/src/dusk/rpg/cutscene/item/ui/cutscenemodaloptionsmarkers.c b/src/dusk/rpg/cutscene/item/ui/cutscenemodaloptionsmarkers.c index af742cc2..8125cc84 100644 --- a/src/dusk/rpg/cutscene/item/ui/cutscenemodaloptionsmarkers.c +++ b/src/dusk/rpg/cutscene/item/ui/cutscenemodaloptionsmarkers.c @@ -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++; } diff --git a/src/dusk/rpg/cutscene/item/ui/cutscenemodaloptionsmarkers.h b/src/dusk/rpg/cutscene/item/ui/cutscenemodaloptionsmarkers.h index 569ea784..bfdc7813 100644 --- a/src/dusk/rpg/cutscene/item/ui/cutscenemodaloptionsmarkers.h +++ b/src/dusk/rpg/cutscene/item/ui/cutscenemodaloptionsmarkers.h @@ -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; diff --git a/src/dusk/rpg/entity/interact/entityinteract.c b/src/dusk/rpg/entity/interact/entityinteract.c index f956c203..063466c0 100644 --- a/src/dusk/rpg/entity/interact/entityinteract.c +++ b/src/dusk/rpg/entity/interact/entityinteract.c @@ -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); diff --git a/src/dusk/rpg/entity/interact/entityinteract.h b/src/dusk/rpg/entity/interact/entityinteract.h index 849d2c06..69f5a4ff 100644 --- a/src/dusk/rpg/entity/interact/entityinteract.h +++ b/src/dusk/rpg/entity/interact/entityinteract.h @@ -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 diff --git a/src/dusk/ui/screen/mainmenu/uimainmenu.c b/src/dusk/ui/screen/mainmenu/uimainmenu.c index dfca5b44..6e8eb8e3 100644 --- a/src/dusk/ui/screen/mainmenu/uimainmenu.c +++ b/src/dusk/ui/screen/mainmenu/uimainmenu.c @@ -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 { diff --git a/src/dusk/ui/screen/mainmenu/uimainmenu.h b/src/dusk/ui/screen/mainmenu/uimainmenu.h index dd352b0d..82f9b9cb 100644 --- a/src/dusk/ui/screen/mainmenu/uimainmenu.h +++ b/src/dusk/ui/screen/mainmenu/uimainmenu.h @@ -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 diff --git a/test/rpg/battle/test_battle.c b/test/rpg/battle/test_battle.c index 2693ac9b..abf19f10 100644 --- a/test/rpg/battle/test_battle.c +++ b/test/rpg/battle/test_battle.c @@ -27,9 +27,10 @@ static cutsceneitem_t CUTSCENE_TEST_SCRATCH_ITEMS[] = { }, { .type = CUTSCENE_ITEM_TYPE_SET_PAUSE, .setPause = CUTSCENE_PAUSE_NONE } }; -static const cutscene_t CUTSCENE_TEST_SCRATCH = { +static cutscene_t CUTSCENE_TEST_SCRATCH = { .items = CUTSCENE_TEST_SCRATCH_ITEMS, .itemCount = sizeof(CUTSCENE_TEST_SCRATCH_ITEMS) / sizeof(cutsceneitem_t), + .itemsMax = sizeof(CUTSCENE_TEST_SCRATCH_ITEMS) / sizeof(cutsceneitem_t), .pause = CUTSCENE_PAUSE_NONE }; diff --git a/test/rpg/cutscene/test_cutscenesystem.c b/test/rpg/cutscene/test_cutscenesystem.c index 8be8bf04..eaf02f32 100644 --- a/test/rpg/cutscene/test_cutscenesystem.c +++ b/test/rpg/cutscene/test_cutscenesystem.c @@ -23,9 +23,10 @@ static void recordCallback(void *userData) { static cutsceneitem_t CUTSCENE_TEST_INNER_ITEMS[] = { { .type = CUTSCENE_ITEM_TYPE_CALLBACK, .callback = recordCallback } }; -static const cutscene_t CUTSCENE_TEST_INNER = { +static cutscene_t CUTSCENE_TEST_INNER = { .items = CUTSCENE_TEST_INNER_ITEMS, .itemCount = sizeof(CUTSCENE_TEST_INNER_ITEMS) / sizeof(cutsceneitem_t), + .itemsMax = sizeof(CUTSCENE_TEST_INNER_ITEMS) / sizeof(cutsceneitem_t), .pause = CUTSCENE_PAUSE_NONE }; @@ -33,12 +34,13 @@ static cutsceneitem_t CUTSCENE_TEST_OUTER_ITEMS[] = { { .type = CUTSCENE_ITEM_TYPE_WAIT, .wait = 0.5f }, { .type = CUTSCENE_ITEM_TYPE_CUTSCENE, - .cutsceneRef = { .cutscene = &CUTSCENE_TEST_INNER, .name = NULL } + .cutsceneRef = { .cutscene = &CUTSCENE_TEST_INNER } } }; -static const cutscene_t CUTSCENE_TEST_OUTER = { +static cutscene_t CUTSCENE_TEST_OUTER = { .items = CUTSCENE_TEST_OUTER_ITEMS, .itemCount = sizeof(CUTSCENE_TEST_OUTER_ITEMS) / sizeof(cutsceneitem_t), + .itemsMax = sizeof(CUTSCENE_TEST_OUTER_ITEMS) / sizeof(cutsceneitem_t), .pause = CUTSCENE_PAUSE_DEFAULT }; @@ -91,9 +93,10 @@ static void test_cutsceneSystemUpdateIsNoopWithNoActiveCutscene(void **state) { static cutsceneitem_t CUTSCENE_TEST_SINGLE_WAIT_ITEMS[] = { { .type = CUTSCENE_ITEM_TYPE_WAIT, .wait = 1.0f } }; -static const cutscene_t CUTSCENE_TEST_SINGLE_WAIT = { +static cutscene_t CUTSCENE_TEST_SINGLE_WAIT = { .items = CUTSCENE_TEST_SINGLE_WAIT_ITEMS, .itemCount = sizeof(CUTSCENE_TEST_SINGLE_WAIT_ITEMS) / sizeof(cutsceneitem_t), + .itemsMax = sizeof(CUTSCENE_TEST_SINGLE_WAIT_ITEMS) / sizeof(cutsceneitem_t), .pause = CUTSCENE_PAUSE_NONE }; @@ -196,6 +199,17 @@ static void test_cutsceneSystemDisposeResetsState(void **state) { } // --- CUTSCENE_INSERT --------------------------------------------------- +// +// INSERT now splices its target's items directly into the running +// scene's own array (see cutsceneSystemInsertCutscene) rather than +// swapping CUTSCENE_SYSTEM.scene to a different cutscene_t and pushing a +// return frame - CUTSCENE_SYSTEM.scene therefore stays the same outer +// cutscene throughout every test below; itemCount growing is what shows +// the splice actually happened. Since the running scene's own array is +// what gets appended into now, any outer cutscene used with INSERT must +// declare its items array (and .itemsMax) with enough spare capacity for +// whatever ends up spliced into it, including transitively through any +// nested INSERT. static uint8_t insertOrderLog[8]; static uint8_t insertOrderLogCount; @@ -211,27 +225,30 @@ static cutsceneitem_t CUTSCENE_TEST_INSERT_SNIPPET_ITEMS[] = { { .type = CUTSCENE_ITEM_TYPE_WAIT, .wait = 2.0f }, { .type = CUTSCENE_ITEM_TYPE_CALLBACK, .callback = recordOrderB } }; -static const cutscene_t CUTSCENE_TEST_INSERT_SNIPPET = { +static cutscene_t CUTSCENE_TEST_INSERT_SNIPPET = { .items = CUTSCENE_TEST_INSERT_SNIPPET_ITEMS, .itemCount = sizeof(CUTSCENE_TEST_INSERT_SNIPPET_ITEMS) / sizeof(cutsceneitem_t), + .itemsMax = + sizeof(CUTSCENE_TEST_INSERT_SNIPPET_ITEMS) / sizeof(cutsceneitem_t), .pause = CUTSCENE_PAUSE_NONE }; -static cutsceneitem_t CUTSCENE_TEST_INSERT_OUTER_ITEMS[] = { +// 5 items to start, +3 spare slots for CUTSCENE_TEST_INSERT_SNIPPET's items. +static cutsceneitem_t CUTSCENE_TEST_INSERT_OUTER_ITEMS[8] = { { .type = CUTSCENE_ITEM_TYPE_CALLBACK, .callback = recordOrder0 }, { .type = CUTSCENE_ITEM_TYPE_WAIT, .wait = 1.0f }, { .type = CUTSCENE_ITEM_TYPE_CALLBACK, .callback = recordOrder1 }, { .type = CUTSCENE_ITEM_TYPE_INSERT, - .insert = { .cutscene = &CUTSCENE_TEST_INSERT_SNIPPET, .name = NULL } + .insert = { .cutscene = &CUTSCENE_TEST_INSERT_SNIPPET } }, { .type = CUTSCENE_ITEM_TYPE_CALLBACK, .callback = recordOrder2 } }; -static const cutscene_t CUTSCENE_TEST_INSERT_OUTER = { +static cutscene_t CUTSCENE_TEST_INSERT_OUTER = { .items = CUTSCENE_TEST_INSERT_OUTER_ITEMS, - .itemCount = - sizeof(CUTSCENE_TEST_INSERT_OUTER_ITEMS) / sizeof(cutsceneitem_t), + .itemCount = 5, + .itemsMax = sizeof(CUTSCENE_TEST_INSERT_OUTER_ITEMS) / sizeof(cutsceneitem_t), .pause = CUTSCENE_PAUSE_NONE }; @@ -246,12 +263,15 @@ static void test_cutsceneInsertSplicesItemsInPlaceThenResumes(void **state) { cutsceneSystemUpdate();// the WAIT elapses -> starts order1 cutsceneSystemUpdate();// order1 done -> starts the INSERT, cascading into orderA - assert_ptr_equal(CUTSCENE_SYSTEM.scene, &CUTSCENE_TEST_INSERT_SNIPPET); + // The splice happened in place - CUTSCENE_SYSTEM.scene never changed, + // but it grew by the snippet's 3 items. + assert_ptr_equal(CUTSCENE_SYSTEM.scene, &CUTSCENE_TEST_INSERT_OUTER); + assert_int_equal(CUTSCENE_SYSTEM.scene->itemCount, 8); TIME.delta = 2.5f;// elapses the snippet's WAIT(2.0) cutsceneSystemUpdate();// orderA done -> starts snippet's WAIT cutsceneSystemUpdate();// snippet's WAIT elapses -> starts orderB - cutsceneSystemUpdate();// orderB done -> pops back, starts order2 + cutsceneSystemUpdate();// orderB done -> continues on to order2 assert_ptr_equal(CUTSCENE_SYSTEM.scene, &CUTSCENE_TEST_INSERT_OUTER); @@ -266,25 +286,29 @@ static void test_cutsceneInsertSplicesItemsInPlaceThenResumes(void **state) { static cutsceneitem_t CUTSCENE_TEST_INSERT_AS_LAST_ITEM_SNIPPET_ITEMS[] = { { .type = CUTSCENE_ITEM_TYPE_CALLBACK, .callback = recordOrderA } }; -static const cutscene_t CUTSCENE_TEST_INSERT_AS_LAST_ITEM_SNIPPET = { +static cutscene_t CUTSCENE_TEST_INSERT_AS_LAST_ITEM_SNIPPET = { .items = CUTSCENE_TEST_INSERT_AS_LAST_ITEM_SNIPPET_ITEMS, .itemCount = sizeof(CUTSCENE_TEST_INSERT_AS_LAST_ITEM_SNIPPET_ITEMS) / sizeof(cutsceneitem_t), + .itemsMax = sizeof(CUTSCENE_TEST_INSERT_AS_LAST_ITEM_SNIPPET_ITEMS) / + sizeof(cutsceneitem_t), .pause = CUTSCENE_PAUSE_NONE }; -static cutsceneitem_t CUTSCENE_TEST_INSERT_AS_LAST_ITEM_OUTER_ITEMS[] = { +// 2 items to start, +1 spare slot for the snippet's single item. +static cutsceneitem_t CUTSCENE_TEST_INSERT_AS_LAST_ITEM_OUTER_ITEMS[3] = { { .type = CUTSCENE_ITEM_TYPE_CALLBACK, .callback = recordOrder0 }, { .type = CUTSCENE_ITEM_TYPE_INSERT, .insert = { - .cutscene = &CUTSCENE_TEST_INSERT_AS_LAST_ITEM_SNIPPET, .name = NULL + .cutscene = &CUTSCENE_TEST_INSERT_AS_LAST_ITEM_SNIPPET } } }; -static const cutscene_t CUTSCENE_TEST_INSERT_AS_LAST_ITEM_OUTER = { +static cutscene_t CUTSCENE_TEST_INSERT_AS_LAST_ITEM_OUTER = { .items = CUTSCENE_TEST_INSERT_AS_LAST_ITEM_OUTER_ITEMS, - .itemCount = sizeof(CUTSCENE_TEST_INSERT_AS_LAST_ITEM_OUTER_ITEMS) / + .itemCount = 2, + .itemsMax = sizeof(CUTSCENE_TEST_INSERT_AS_LAST_ITEM_OUTER_ITEMS) / sizeof(cutsceneitem_t), .pause = CUTSCENE_PAUSE_NONE }; @@ -298,12 +322,11 @@ static void test_cutsceneInsertAsLastItemEndsCutsceneNaturally( cutsceneSystemStartCutscene(&CUTSCENE_TEST_INSERT_AS_LAST_ITEM_OUTER); cutsceneSystemUpdate();// order0 done -> starts INSERT, cascades to orderA - assert_ptr_equal( - CUTSCENE_SYSTEM.scene, &CUTSCENE_TEST_INSERT_AS_LAST_ITEM_SNIPPET - ); + assert_ptr_equal(CUTSCENE_SYSTEM.scene, &CUTSCENE_TEST_INSERT_AS_LAST_ITEM_OUTER); + assert_int_equal(CUTSCENE_SYSTEM.scene->itemCount, 3); - // orderA is the snippet's only item, and the snippet is the outer's last - // item -- finishing it should pop straight through to the natural end + // orderA is the snippet's only item, and the snippet was spliced in as + // the outer's last item -- finishing it should reach the natural end // of the whole cutscene, not get stuck. cutsceneSystemUpdate(); assert_null(CUTSCENE_SYSTEM.scene); @@ -316,42 +339,51 @@ static void test_cutsceneInsertAsLastItemEndsCutsceneNaturally( static cutsceneitem_t CUTSCENE_TEST_INSERT_NESTED_INNER_ITEMS[] = { { .type = CUTSCENE_ITEM_TYPE_CALLBACK, .callback = recordOrderA } }; -static const cutscene_t CUTSCENE_TEST_INSERT_NESTED_INNER = { +static cutscene_t CUTSCENE_TEST_INSERT_NESTED_INNER = { .items = CUTSCENE_TEST_INSERT_NESTED_INNER_ITEMS, .itemCount = sizeof(CUTSCENE_TEST_INSERT_NESTED_INNER_ITEMS) / sizeof(cutsceneitem_t), + .itemsMax = + sizeof(CUTSCENE_TEST_INSERT_NESTED_INNER_ITEMS) / sizeof(cutsceneitem_t), .pause = CUTSCENE_PAUSE_NONE }; // INSERT is MID's first item, so entering MID (from OUTER) cascades // straight through into INNER within the same Start chain, one level -// deeper than a single insert. +// deeper than a single insert. MID is only ever read from as a splice +// source (see CUTSCENE_TEST_INSERT_NESTED_OUTER) - it's never itself the +// running scene, so it needs no spare capacity of its own. static cutsceneitem_t CUTSCENE_TEST_INSERT_NESTED_MID_ITEMS[] = { { .type = CUTSCENE_ITEM_TYPE_INSERT, - .insert = { .cutscene = &CUTSCENE_TEST_INSERT_NESTED_INNER, .name = NULL } + .insert = { .cutscene = &CUTSCENE_TEST_INSERT_NESTED_INNER } }, { .type = CUTSCENE_ITEM_TYPE_CALLBACK, .callback = recordOrderB } }; -static const cutscene_t CUTSCENE_TEST_INSERT_NESTED_MID = { +static cutscene_t CUTSCENE_TEST_INSERT_NESTED_MID = { .items = CUTSCENE_TEST_INSERT_NESTED_MID_ITEMS, .itemCount = sizeof(CUTSCENE_TEST_INSERT_NESTED_MID_ITEMS) / sizeof(cutsceneitem_t), + .itemsMax = + sizeof(CUTSCENE_TEST_INSERT_NESTED_MID_ITEMS) / sizeof(cutsceneitem_t), .pause = CUTSCENE_PAUSE_NONE }; -static cutsceneitem_t CUTSCENE_TEST_INSERT_NESTED_OUTER_ITEMS[] = { +// 3 items to start, +2 spare for MID's items, +1 more for MID's own +// nested INSERT(INNER) once MID's items become part of this array too. +static cutsceneitem_t CUTSCENE_TEST_INSERT_NESTED_OUTER_ITEMS[6] = { { .type = CUTSCENE_ITEM_TYPE_CALLBACK, .callback = recordOrder0 }, { .type = CUTSCENE_ITEM_TYPE_INSERT, - .insert = { .cutscene = &CUTSCENE_TEST_INSERT_NESTED_MID, .name = NULL } + .insert = { .cutscene = &CUTSCENE_TEST_INSERT_NESTED_MID } }, { .type = CUTSCENE_ITEM_TYPE_CALLBACK, .callback = recordOrder2 } }; -static const cutscene_t CUTSCENE_TEST_INSERT_NESTED_OUTER = { +static cutscene_t CUTSCENE_TEST_INSERT_NESTED_OUTER = { .items = CUTSCENE_TEST_INSERT_NESTED_OUTER_ITEMS, - .itemCount = - sizeof(CUTSCENE_TEST_INSERT_NESTED_OUTER_ITEMS) / sizeof(cutsceneitem_t), + .itemCount = 3, + .itemsMax = sizeof(CUTSCENE_TEST_INSERT_NESTED_OUTER_ITEMS) / + sizeof(cutsceneitem_t), .pause = CUTSCENE_PAUSE_NONE }; @@ -362,16 +394,14 @@ static void test_cutsceneInsertNestsThroughMultipleLevels(void **state) { cutsceneSystemStartCutscene(&CUTSCENE_TEST_INSERT_NESTED_OUTER); // order0 done -> starts INSERT(MID), cascading through INSERT(INNER) -> - // orderA, all within this one Update call's Start chain. + // orderA, all within this one Update call's Start chain - both splices + // land in OUTER's own array. cutsceneSystemUpdate(); - assert_ptr_equal(CUTSCENE_SYSTEM.scene, &CUTSCENE_TEST_INSERT_NESTED_INNER); - - cutsceneSystemUpdate();// orderA done -> pops to MID, starts orderB - assert_ptr_equal(CUTSCENE_SYSTEM.scene, &CUTSCENE_TEST_INSERT_NESTED_MID); - - cutsceneSystemUpdate();// orderB done -> pops to OUTER, starts order2 assert_ptr_equal(CUTSCENE_SYSTEM.scene, &CUTSCENE_TEST_INSERT_NESTED_OUTER); + assert_int_equal(CUTSCENE_SYSTEM.scene->itemCount, 6); + cutsceneSystemUpdate();// orderA done -> continues on to orderB + cutsceneSystemUpdate();// orderB done -> continues on to order2 cutsceneSystemUpdate();// order2 done -> ends naturally assert_null(CUTSCENE_SYSTEM.scene); @@ -380,87 +410,37 @@ static void test_cutsceneInsertNestsThroughMultipleLevels(void **state) { assert_memory_equal(insertOrderLog, expected, sizeof(expected)); } -static cutsceneitem_t CUTSCENE_TEST_INSERT_OVERFLOW_LEVEL5_ITEMS[] = { +static cutsceneitem_t CUTSCENE_TEST_INSERT_NO_CAPACITY_SNIPPET_ITEMS[] = { { .type = CUTSCENE_ITEM_TYPE_CALLBACK, .callback = recordOrderA } }; -static const cutscene_t CUTSCENE_TEST_INSERT_OVERFLOW_LEVEL5 = { - .items = CUTSCENE_TEST_INSERT_OVERFLOW_LEVEL5_ITEMS, - .itemCount = sizeof(CUTSCENE_TEST_INSERT_OVERFLOW_LEVEL5_ITEMS) / - sizeof(cutsceneitem_t), - .pause = CUTSCENE_PAUSE_NONE -}; -static cutsceneitem_t CUTSCENE_TEST_INSERT_OVERFLOW_LEVEL4_ITEMS[] = { - { - .type = CUTSCENE_ITEM_TYPE_INSERT, - .insert = { .cutscene = &CUTSCENE_TEST_INSERT_OVERFLOW_LEVEL5, .name = NULL } - } -}; -static const cutscene_t CUTSCENE_TEST_INSERT_OVERFLOW_LEVEL4 = { - .items = CUTSCENE_TEST_INSERT_OVERFLOW_LEVEL4_ITEMS, - .itemCount = sizeof(CUTSCENE_TEST_INSERT_OVERFLOW_LEVEL4_ITEMS) / - sizeof(cutsceneitem_t), - .pause = CUTSCENE_PAUSE_NONE -}; -static cutsceneitem_t CUTSCENE_TEST_INSERT_OVERFLOW_LEVEL3_ITEMS[] = { - { - .type = CUTSCENE_ITEM_TYPE_INSERT, - .insert = { .cutscene = &CUTSCENE_TEST_INSERT_OVERFLOW_LEVEL4, .name = NULL } - } -}; -static const cutscene_t CUTSCENE_TEST_INSERT_OVERFLOW_LEVEL3 = { - .items = CUTSCENE_TEST_INSERT_OVERFLOW_LEVEL3_ITEMS, - .itemCount = sizeof(CUTSCENE_TEST_INSERT_OVERFLOW_LEVEL3_ITEMS) / - sizeof(cutsceneitem_t), - .pause = CUTSCENE_PAUSE_NONE -}; -static cutsceneitem_t CUTSCENE_TEST_INSERT_OVERFLOW_LEVEL2_ITEMS[] = { - { - .type = CUTSCENE_ITEM_TYPE_INSERT, - .insert = { .cutscene = &CUTSCENE_TEST_INSERT_OVERFLOW_LEVEL3, .name = NULL } - } -}; -static const cutscene_t CUTSCENE_TEST_INSERT_OVERFLOW_LEVEL2 = { - .items = CUTSCENE_TEST_INSERT_OVERFLOW_LEVEL2_ITEMS, - .itemCount = sizeof(CUTSCENE_TEST_INSERT_OVERFLOW_LEVEL2_ITEMS) / - sizeof(cutsceneitem_t), - .pause = CUTSCENE_PAUSE_NONE -}; -static cutsceneitem_t CUTSCENE_TEST_INSERT_OVERFLOW_LEVEL1_ITEMS[] = { - { - .type = CUTSCENE_ITEM_TYPE_INSERT, - .insert = { .cutscene = &CUTSCENE_TEST_INSERT_OVERFLOW_LEVEL2, .name = NULL } - } -}; -static const cutscene_t CUTSCENE_TEST_INSERT_OVERFLOW_LEVEL1 = { - .items = CUTSCENE_TEST_INSERT_OVERFLOW_LEVEL1_ITEMS, - .itemCount = sizeof(CUTSCENE_TEST_INSERT_OVERFLOW_LEVEL1_ITEMS) / - sizeof(cutsceneitem_t), - .pause = CUTSCENE_PAUSE_NONE -}; -static cutsceneitem_t CUTSCENE_TEST_INSERT_OVERFLOW_LEVEL0_ITEMS[] = { - { - .type = CUTSCENE_ITEM_TYPE_INSERT, - .insert = { .cutscene = &CUTSCENE_TEST_INSERT_OVERFLOW_LEVEL1, .name = NULL } - } -}; -static const cutscene_t CUTSCENE_TEST_INSERT_OVERFLOW_LEVEL0 = { - .items = CUTSCENE_TEST_INSERT_OVERFLOW_LEVEL0_ITEMS, - .itemCount = sizeof(CUTSCENE_TEST_INSERT_OVERFLOW_LEVEL0_ITEMS) / - sizeof(cutsceneitem_t), +static cutscene_t CUTSCENE_TEST_INSERT_NO_CAPACITY_SNIPPET = { + .items = CUTSCENE_TEST_INSERT_NO_CAPACITY_SNIPPET_ITEMS, + .itemCount = 1, + .itemsMax = 1, .pause = CUTSCENE_PAUSE_NONE }; -static void test_cutsceneInsertAssertsWhenNestingExceedsStackMax( - void **state -) { +// No spare capacity at all - itemsMax equals itemCount, leaving no room +// to splice anything into it. +static cutsceneitem_t CUTSCENE_TEST_INSERT_NO_CAPACITY_OUTER_ITEMS[1] = { + { + .type = CUTSCENE_ITEM_TYPE_INSERT, + .insert = { .cutscene = &CUTSCENE_TEST_INSERT_NO_CAPACITY_SNIPPET } + } +}; +static cutscene_t CUTSCENE_TEST_INSERT_NO_CAPACITY_OUTER = { + .items = CUTSCENE_TEST_INSERT_NO_CAPACITY_OUTER_ITEMS, + .itemCount = 1, + .itemsMax = 1, + .pause = CUTSCENE_PAUSE_NONE +}; + +static void test_cutsceneInsertAssertsWhenExceedingItemsMax(void **state) { cutsceneSystemInit(); - // 5 levels of INSERT deep, one past CUTSCENE_INSERT_STACK_MAX (4) -- - // all cascade synchronously within this one call, since each level's - // only item is the INSERT starting immediately. expect_assert_failure( - cutsceneSystemStartCutscene(&CUTSCENE_TEST_INSERT_OVERFLOW_LEVEL0) + cutsceneSystemStartCutscene(&CUTSCENE_TEST_INSERT_NO_CAPACITY_OUTER) ); } @@ -469,22 +449,26 @@ static void noopInsertCallback(void *userData) {} static cutsceneitem_t CUTSCENE_TEST_INSERT_PERSIST_SNIPPET_ITEMS[] = { { .type = CUTSCENE_ITEM_TYPE_CALLBACK, .callback = noopInsertCallback } }; -static const cutscene_t CUTSCENE_TEST_INSERT_PERSIST_SNIPPET = { +static cutscene_t CUTSCENE_TEST_INSERT_PERSIST_SNIPPET = { .items = CUTSCENE_TEST_INSERT_PERSIST_SNIPPET_ITEMS, .itemCount = sizeof(CUTSCENE_TEST_INSERT_PERSIST_SNIPPET_ITEMS) / sizeof(cutsceneitem_t), + .itemsMax = sizeof(CUTSCENE_TEST_INSERT_PERSIST_SNIPPET_ITEMS) / + sizeof(cutsceneitem_t), .pause = CUTSCENE_PAUSE_ALL }; -static cutsceneitem_t CUTSCENE_TEST_INSERT_PERSIST_OUTER_ITEMS[] = { +// 1 item to start, +1 spare slot for the snippet's single item. +static cutsceneitem_t CUTSCENE_TEST_INSERT_PERSIST_OUTER_ITEMS[2] = { { .type = CUTSCENE_ITEM_TYPE_INSERT, - .insert = { .cutscene = &CUTSCENE_TEST_INSERT_PERSIST_SNIPPET, .name = NULL } + .insert = { .cutscene = &CUTSCENE_TEST_INSERT_PERSIST_SNIPPET } } }; -static const cutscene_t CUTSCENE_TEST_INSERT_PERSIST_OUTER = { +static cutscene_t CUTSCENE_TEST_INSERT_PERSIST_OUTER = { .items = CUTSCENE_TEST_INSERT_PERSIST_OUTER_ITEMS, - .itemCount = sizeof(CUTSCENE_TEST_INSERT_PERSIST_OUTER_ITEMS) / + .itemCount = 1, + .itemsMax = sizeof(CUTSCENE_TEST_INSERT_PERSIST_OUTER_ITEMS) / sizeof(cutsceneitem_t), .pause = CUTSCENE_PAUSE_NPC }; @@ -501,10 +485,10 @@ static void test_cutsceneInsertDoesNotResetPauseOrInteractEntities( &CUTSCENE_TEST_INSERT_PERSIST_OUTER, &ENTITIES[3], &ENTITIES[4] ); - // The INSERT item's Start already cascaded into the snippet by now. - assert_ptr_equal( - CUTSCENE_SYSTEM.scene, &CUTSCENE_TEST_INSERT_PERSIST_SNIPPET - ); + // The INSERT item's Start already spliced the snippet into place and + // cascaded into it by now. + assert_ptr_equal(CUTSCENE_SYSTEM.scene, &CUTSCENE_TEST_INSERT_PERSIST_OUTER); + assert_int_equal(CUTSCENE_SYSTEM.scene->itemCount, 2); assert_ptr_equal(CUTSCENE_SYSTEM.entityInteract, &ENTITIES[3]); assert_ptr_equal(CUTSCENE_SYSTEM.entityInteracted, &ENTITIES[4]); // The snippet declares ALL, but INSERT doesn't apply it -- the outer @@ -526,7 +510,7 @@ int main(int argc, char** argv) { cmocka_unit_test(test_cutsceneInsertSplicesItemsInPlaceThenResumes), cmocka_unit_test(test_cutsceneInsertAsLastItemEndsCutsceneNaturally), cmocka_unit_test(test_cutsceneInsertNestsThroughMultipleLevels), - cmocka_unit_test(test_cutsceneInsertAssertsWhenNestingExceedsStackMax), + cmocka_unit_test(test_cutsceneInsertAssertsWhenExceedingItemsMax), cmocka_unit_test(test_cutsceneInsertDoesNotResetPauseOrInteractEntities), }; diff --git a/test/rpg/entity/test_entityinteract.c b/test/rpg/entity/test_entityinteract.c index cf032bd2..4980e21f 100644 --- a/test/rpg/entity/test_entityinteract.c +++ b/test/rpg/entity/test_entityinteract.c @@ -14,9 +14,10 @@ static cutsceneitem_t CUTSCENE_TEST_INTERACT_ITEMS[] = { { .type = CUTSCENE_ITEM_TYPE_WAIT, .wait = 1.0f } }; -static const cutscene_t CUTSCENE_TEST_INTERACT = { +static cutscene_t CUTSCENE_TEST_INTERACT = { .items = CUTSCENE_TEST_INTERACT_ITEMS, .itemCount = 1, + .itemsMax = 1, .pause = CUTSCENE_PAUSE_NONE };