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
-7
View File
@@ -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": { "moves": {
"fire": { "fire": {
"ability": "FIRE", "ability": "FIRE",
"power": 12, "power": 12,
"mpCost": 4, "mpCost": 4,
"target": "SINGLE_ENEMY", "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" "cutscene": "battle/fire"
} }
} }
+25 -16
View File
@@ -86,9 +86,10 @@ errorret_t cutsceneParseDoc(
// Everything structural has validated by this point, so it's safe to // Everything structural has validated by this point, so it's safe to
// start overwriting items - a per-item parse failure past this point // start overwriting items - a per-item parse failure past this point
// still leaves it partially overwritten, but that's fine: any load // still leaves it partially overwritten, but that's fine: any load
// failure here already sends the game into the fatal-error overlay // failure here already sends the game into the fatal-error overlay (see
// (see cutsceneSystemLoad/cutsceneCutsceneResolve/cutsceneInsertResolve), // cutsceneSystemLoad/cutsceneCutsceneResolve/
// so nothing keeps running against stale data. // cutsceneInsertResolveAndSplice), so nothing keeps running against
// stale data.
memoryZero(items, itemCount * sizeof(cutsceneitem_t)); memoryZero(items, itemCount * sizeof(cutsceneitem_t));
yyjson_arr_iter itemIter = yyjson_arr_iter_with(itemsArr); yyjson_arr_iter itemIter = yyjson_arr_iter_with(itemsArr);
@@ -101,24 +102,14 @@ errorret_t cutsceneParseDoc(
} }
scene->itemCount = (uint8_t)itemCount; scene->itemCount = (uint8_t)itemCount;
scene->itemsMax = itemsMax;
scene->pause = pause; scene->pause = pause;
errorOk(); errorOk();
} }
void cutsceneAppend( void cutsceneAppendAt(
cutscene_t *scene, 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 uint8_t atIndex,
const cutsceneitem_t *items, const cutsceneitem_t *items,
const uint8_t count const uint8_t count
@@ -128,7 +119,7 @@ void cutsceneAppendNext(
assertTrue(count > 0, "Count cannot be 0"); assertTrue(count > 0, "Count cannot be 0");
assertTrue(atIndex <= scene->itemCount, "atIndex out of bounds"); assertTrue(atIndex <= scene->itemCount, "atIndex out of bounds");
assertTrue( assertTrue(
(size_t)scene->itemCount + count <= itemsMax, (size_t)scene->itemCount + count <= scene->itemsMax,
"Cutscene item append exceeds itemsMax" "Cutscene item append exceeds itemsMax"
); );
@@ -143,3 +134,21 @@ void cutsceneAppendNext(
memoryCopy(&scene->items[atIndex], items, count * sizeof(cutsceneitem_t)); memoryCopy(&scene->items[atIndex], items, count * sizeof(cutsceneitem_t));
scene->itemCount += count; 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 { typedef struct cutscene_s {
cutsceneitem_t *items; cutsceneitem_t *items;
uint8_t itemCount; 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; cutscenepause_t pause;
} cutscene_t; } cutscene_t;
@@ -47,15 +54,17 @@ size_t cutsceneCountItems(yyjson_doc *doc);
/** /**
* Parses doc's "items" (and optional "pause") into items, and updates * Parses doc's "items" (and optional "pause") into items, and updates
* scene's itemCount/pause - scene->items is left untouched, since the * scene's itemCount/itemsMax/pause - scene->items is left untouched, since
* caller already points it at items. Purely a parser: doesn't touch the * the caller already points it at items. Purely a parser: doesn't touch
* asset system, doesn't lock/load anything, and doesn't take ownership of * the asset system, doesn't lock/load anything, and doesn't take
* doc - the caller is responsible for both loading doc beforehand and for * ownership of doc - the caller is responsible for both loading doc
* its lifetime afterward (name/string fields written into items point * beforehand and for its lifetime afterward (though as of cutscene items
* directly into it, so it must outlive items' use). * 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 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 items Destination item array, capacity itemsMax.
* @param itemsMax Capacity of items. * @param itemsMax Capacity of items.
* @return Error code indicating success or failure of the parse. * @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. * Inserts items into scene->items starting at atIndex, growing
* scene->items must point at a buffer with capacity itemsMax - asserts if * scene->itemCount. Every existing item from atIndex onward shifts right
* appending would exceed it. * by count to make room. Asserts if this would exceed scene->itemsMax.
*
* @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.
* *
* @param scene Cutscene to insert into. * @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 * @param atIndex Index the first inserted item should end up at - must be
* <= scene->itemCount. * <= scene->itemCount.
* @param items Items to insert, copied in order. * @param items Items to insert, copied in order.
* @param count Number of entries in items. * @param count Number of entries in items.
*/ */
void cutsceneAppendNext( void cutsceneAppendAt(
cutscene_t *scene, cutscene_t *scene,
const size_t itemsMax,
const uint8_t atIndex, const uint8_t atIndex,
const cutsceneitem_t *items, const cutsceneitem_t *items,
const uint8_t count 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; 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; 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() { void cutsceneSystemInit() {
memoryZero(&CUTSCENE_SYSTEM, sizeof(cutscenesystem_t)); 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( void cutsceneSystemPrepare(
const cutscene_t *cutscene, cutscene_t *cutscene,
entity_t *interact, entity_t *interact,
entity_t *interacted entity_t *interacted
) { ) {
@@ -62,15 +56,14 @@ void cutsceneSystemPrepare(
CUTSCENE_SYSTEM.textCache[0] = '\0'; CUTSCENE_SYSTEM.textCache[0] = '\0';
CUTSCENE_SYSTEM.currentItem = 0xFF;// Set to 0xFF so Next wraps to 0. CUTSCENE_SYSTEM.currentItem = 0xFF;// Set to 0xFF so Next wraps to 0.
CUTSCENE_SYSTEM.onComplete = NULL; CUTSCENE_SYSTEM.onComplete = NULL;
CUTSCENE_SYSTEM.insertStackSize = 0;
} }
void cutsceneSystemStartCutscene(const cutscene_t *cutscene) { void cutsceneSystemStartCutscene(cutscene_t *cutscene) {
cutsceneSystemStartCutsceneWith(cutscene, NULL, NULL); cutsceneSystemStartCutsceneWith(cutscene, NULL, NULL);
} }
void cutsceneSystemStartCutsceneWith( void cutsceneSystemStartCutsceneWith(
const cutscene_t *cutscene, cutscene_t *cutscene,
entity_t *interact, entity_t *interact,
entity_t *interacted entity_t *interacted
) { ) {
@@ -79,13 +72,26 @@ void cutsceneSystemStartCutsceneWith(
} }
void cutsceneSystemStartCutsceneAndGoToMarker( void cutsceneSystemStartCutsceneAndGoToMarker(
const cutscene_t *cutscene, cutscene_t *cutscene,
const char_t *marker const char_t *marker
) { ) {
cutsceneSystemPrepare(cutscene, NULL, NULL); cutsceneSystemPrepare(cutscene, NULL, NULL);
cutsceneGoTo(marker); 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) { void cutsceneSystemLoad(const char_t *file) {
char_t path[ASSET_FILE_NAME_MAX]; char_t path[ASSET_FILE_NAME_MAX];
stringFormat(path, sizeof(path), "cutscenes/%s", file); 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 // Steal the parsed doc before unlocking - assetJsonDispose would
// otherwise free it out from under CUTSCENE_LOADED_ITEMS' name pointers // otherwise free it out before it's been parsed below.
// the moment this entry gets reaped.
yyjson_doc *doc = entry->data.json; yyjson_doc *doc = entry->data.json;
entry->data.json = NULL; entry->data.json = NULL;
assetUnlockEntry(entry); assetUnlockEntry(entry);
@@ -110,12 +115,12 @@ void cutsceneSystemLoad(const char_t *file) {
// trigger a reap - assetRequireLoaded treats an already-LOADED entry as // trigger a reap - assetRequireLoaded treats an already-LOADED entry as
// an instant cache hit with no reparsing, so a stale leftover entry for // 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 // this same path would otherwise make a later load of it wrongly skip
// reloading, still returning CUTSCENE_LOADED_SCENE even after some // reloading, still returning CUTSCENE_SYSTEM.loadedScene even after
// other cutscene has since overwritten it. // some other cutscene has since overwritten it.
assetReapUnused(); assetReapUnused();
errorret_t parseResult = cutsceneParseDoc( errorret_t parseResult = cutsceneParseDoc(
doc, &CUTSCENE_LOADED_SCENE, CUTSCENE_LOADED_ITEMS, doc, &CUTSCENE_SYSTEM.loadedScene, CUTSCENE_SYSTEM.loadedItems,
CUTSCENE_LOADED_ITEMS_MAX CUTSCENE_LOADED_ITEMS_MAX
); );
if(errorIsNotOk(parseResult)) { if(errorIsNotOk(parseResult)) {
@@ -126,7 +131,12 @@ void cutsceneSystemLoad(const char_t *file) {
} }
cutsceneLoadedSetDoc(doc); 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) { void cutsceneRestart(void) {
@@ -146,88 +156,6 @@ void cutsceneRestart(void) {
CUTSCENE_SYSTEM.onComplete = onComplete; 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) { void cutsceneSystemSetOnComplete(cutscenecallback_t onComplete) {
assertNotNull( assertNotNull(
CUTSCENE_SYSTEM.scene, CUTSCENE_SYSTEM.scene,
@@ -236,33 +164,6 @@ void cutsceneSystemSetOnComplete(cutscenecallback_t onComplete) {
CUTSCENE_SYSTEM.onComplete = 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 * cutsceneSystemGetEntity(const uint8_t entityIndex) {
entity_t *entity; entity_t *entity;
@@ -335,20 +236,77 @@ void cutsceneSystemSetTextCache(const char_t *text) {
); );
} }
void cutsceneSystemDispose() { void cutsceneSystemNext() {
CUTSCENE_SYSTEM.scene = NULL; if(CUTSCENE_SYSTEM.scene == NULL) return;
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;
cutsceneLoadedSetDoc(NULL); CUTSCENE_SYSTEM.currentItem++;
cutsceneInsertSetDoc(NULL);
// 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];
} }
+77 -119
View File
@@ -10,100 +10,26 @@
typedef struct entity_s entity_t; typedef struct entity_s entity_t;
#define CUTSCENE_ENTITY_INTERACT ((uint8_t)0xFE) // Sentinels accepted in place of a literal index/ID by the cutsceneSystemGet*
#define CUTSCENE_ENTITY_INTERACTED ((uint8_t)0xFD) // resolvers below.
#define CUTSCENE_ENTITY_LAST_CREATED ((uint8_t)0xFC) #define CUTSCENE_ENTITY_INTERACT ((uint8_t)0xFE)
#define CUTSCENE_ENTITY_LAST_REF ((uint8_t)0xFB) #define CUTSCENE_ENTITY_INTERACTED ((uint8_t)0xFD)
#define CUTSCENE_AREA_LAST_CREATED ((uint8_t)0xFF) #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) #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 #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 #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 #define CUTSCENE_LOADED_ITEMS_MAX 128
extern cutsceneitem_t CUTSCENE_LOADED_ITEMS[CUTSCENE_LOADED_ITEMS_MAX]; // Scratch capacity for resolving a JSON-authored CUTSCENE_ITEM_TYPE_INSERT
// target - see cutsceneSystemInsertCutscene. Sized small deliberately -
// The cutscene_t for whatever was last parsed into CUTSCENE_LOADED_ITEMS - // an inserted snippet is meant to be a short splice, not a full cutscene.
// .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.
#define CUTSCENE_INSERT_ITEMS_MAX 16 #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 { typedef struct {
const cutscene_t *scene; cutscene_t *scene;
uint8_t currentItem;
} cutsceneinsertframe_t;
typedef struct {
const cutscene_t *scene;
uint8_t currentItem; uint8_t currentItem;
cutscenepause_t pause; cutscenepause_t pause;
entity_t *entityInteract; entity_t *entityInteract;
@@ -119,7 +45,7 @@ typedef struct {
// may read or write it. // may read or write it.
char_t textCache[CUTSCENE_TEXT_CACHE_MAX]; char_t textCache[CUTSCENE_TEXT_CACHE_MAX];
// Data (used by the current item). // Runtime data for the current item.
cutsceneitemdata_t data; cutsceneitemdata_t data;
// Custom user data for the running cutscene. // Custom user data for the running cutscene.
@@ -128,11 +54,12 @@ typedef struct {
// See cutsceneSystemSetOnComplete. // See cutsceneSystemSetOnComplete.
cutscenecallback_t onComplete; cutscenecallback_t onComplete;
// Return stack pushed by CUTSCENE_ITEM_TYPE_INSERT items - see // The fixed buffer/scene pair cutsceneSystemLoad/cutsceneCutsceneResolve
// cutsceneSystemInsertCutscene. Reset (insertStackSize = 0) whenever a // parse a JSON-authored cutscene into - see CUTSCENE_LOADED_ITEMS_MAX
// cutscene freshly starts or the system is disposed. // above. loadedScene.items always points at loadedItems; loadedScene's
cutsceneinsertframe_t insertStack[CUTSCENE_INSERT_STACK_MAX]; // itemCount/itemsMax/pause are overwritten on each load.
uint8_t insertStackSize; cutsceneitem_t loadedItems[CUTSCENE_LOADED_ITEMS_MAX];
cutscene_t loadedScene;
} cutscenesystem_t; } cutscenesystem_t;
extern cutscenesystem_t CUTSCENE_SYSTEM; extern cutscenesystem_t CUTSCENE_SYSTEM;
@@ -142,12 +69,36 @@ extern cutscenesystem_t CUTSCENE_SYSTEM;
*/ */
void cutsceneSystemInit(); 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. * Start a cutscene with no bound entities.
* *
* @param cutscene Pointer to the cutscene to start. * @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. * 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). * @param interacted The entity that was interacted with (NPC).
*/ */
void cutsceneSystemStartCutsceneWith( void cutsceneSystemStartCutsceneWith(
const cutscene_t *cutscene, cutscene_t *cutscene,
entity_t *interact, entity_t *interact,
entity_t *interacted entity_t *interacted
); );
@@ -173,29 +124,29 @@ void cutsceneSystemStartCutsceneWith(
* @param marker Marker name to jump to, matched with stringEquals. * @param marker Marker name to jump to, matched with stringEquals.
*/ */
void cutsceneSystemStartCutsceneAndGoToMarker( void cutsceneSystemStartCutsceneAndGoToMarker(
const cutscene_t *cutscene, cutscene_t *cutscene,
const char_t *marker const char_t *marker
); );
/** /**
* Splices cutscene's items into the running cutscene in place, resuming * Splices cutscene's items into the running cutscene (CUTSCENE_SYSTEM.scene)
* the current cutscene right after the CUTSCENE_ITEM_TYPE_INSERT item that * in place, right after the currently-executing item, via cutsceneAppendNext
* called this once cutscene's items run out - unlike * - unlike cutsceneSystemStartCutscene (a one-way jump that replaces the
* cutsceneSystemStartCutscene (a one-way jump that replaces the running * running cutscene outright), the inserted items simply become part of the
* cutscene outright). Pushes a return frame onto * running scene's own item array, so there's nothing separate to "return"
* CUTSCENE_SYSTEM.insertStack; cutsceneSystemNext pops it once cutscene * to once they finish - whatever already followed the CUTSCENE_ITEM_TYPE_
* ends. Deliberately does not touch pause flags, interact entities, * INSERT item continues normally, now shifted further down the same array.
* "last created" state, the text cache or userData - those all keep * Then immediately advances into the first inserted item (see
* whatever the outer cutscene set, since this is meant to feel like * cutsceneSystemNext), so the splice takes effect within this same call.
* pasting cutscene's items in place rather than starting an independent * Deliberately does not touch pause flags, interact entities, "last
* cutscene. Because of this, cutscene's own .pause is ignored. * created" state, the text cache or userData - those all keep whatever the
* Asserts if no cutscene is currently running, or if inserts are already * outer cutscene set, since this is meant to feel like pasting cutscene's
* nested CUTSCENE_INSERT_STACK_MAX deep. * items in place rather than starting an independent cutscene. Because of
* * this, cutscene's own .pause is ignored. Asserts if no cutscene is
* Note: cutsceneRestart() resets the insert stack (via * currently running, or if the running scene's own itemsMax has no room
* cutsceneSystemPrepare), so a RESTART item used from inside an inserted * left for cutscene's items (see cutscene_t.itemsMax's doc comment - a
* cutscene restarts just that inserted cutscene and does not return to * cutscene that expects to have items inserted into it at runtime needs to
* whatever it was inserted into. * be declared with spare capacity up front).
* *
* @param cutscene The cutscene whose items to splice in. * @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. * Loads and immediately starts a cutscene asset by file name, e.g.
* cutsceneSystemLoad("main_menu.jsonc") loads and starts * cutsceneSystemLoad("main_menu.jsonc") loads and starts
* assets/cutscenes/main_menu.jsonc, parsing it into * assets/cutscenes/main_menu.jsonc, parsing it into
* CUTSCENE_LOADED_SCENE/ITEMS - the underlying JSON asset entry is * CUTSCENE_SYSTEM.loadedScene/loadedItems - the underlying JSON asset
* unlocked and reaped immediately once that's done (see * entry is unlocked and reaped immediately once that's done (see
* CUTSCENE_LOADED_ITEMS_MAX's doc comment for why), so it's always * 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 * re-read+re-parsed fresh, never assumed still resident from a previous
* call. Opens the fatal error overlay (see uiFatalErrorOpen) instead of * 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); 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, * Restarts the currently running cutscene from its first item,
* preserving whatever interact/interacted entities triggered it. * preserving whatever interact/interacted entities triggered it.
@@ -322,8 +285,3 @@ void cutsceneSystemUpdate();
* @return Pointer to the current cutscene item. * @return Pointer to the current cutscene item.
*/ */
const cutsceneitem_t * cutsceneSystemGetCurrentItem(); 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/cutsceneitem.h"
#include "rpg/cutscene/item/json/cutscenejsonfield.h"
void cutsceneMarkerStart( void cutsceneMarkerStart(
const cutsceneitem_t *item, const cutsceneitem_t *item,
@@ -24,11 +25,11 @@ errorret_t cutsceneMarkerLoad(
yyjson_val *itemObj, yyjson_val *itemObj,
cutsceneitem_t *item cutsceneitem_t *item
) { ) {
const char_t *name = yyjson_get_str(yyjson_obj_get(itemObj, "name")); if(!cutsceneJsonCopyString(
if(name == NULL) { itemObj, "name", NULL, item->marker.name, CUTSCENE_MARKER_NAME_MAX
errorThrow("Cutscene item (MARKER): missing 'name'"); )) {
errorThrow("Cutscene item (MARKER): missing/too-long 'name'");
} }
item->marker.name = name;
errorOk(); errorOk();
} }
@@ -8,8 +8,13 @@
#pragma once #pragma once
#include "rpg/cutscene/item/cutsceneitembase.h" #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 { typedef struct {
const char_t *name; char_t name[CUTSCENE_MARKER_NAME_MAX];
} cutscenemarker_t; } cutscenemarker_t;
/** /**
+27 -31
View File
@@ -377,8 +377,8 @@ void cutsceneCutsceneStart(
const cutsceneitem_t *item, const cutsceneitem_t *item,
cutsceneitemdata_t *data cutsceneitemdata_t *data
) { ) {
const cutscene_t *cutscene = item->cutsceneRef.cutscene; cutscene_t *cutscene = item->cutsceneRef.cutscene;
if(cutscene == NULL && item->cutsceneRef.name != NULL) { if(cutscene == NULL && item->cutsceneRef.name[0] != '\0') {
// Not cached back into item - with only one shared, fixed load buffer // Not cached back into item - with only one shared, fixed load buffer
// (see cutscenesystem.h's CUTSCENE_LOADED_ITEMS_MAX doc comment), // (see cutscenesystem.h's CUTSCENE_LOADED_ITEMS_MAX doc comment),
// every JSON-authored reference resolves to the same fixed address // every JSON-authored reference resolves to the same fixed address
@@ -397,7 +397,7 @@ bool_t cutsceneCutsceneUpdate(
return false; return false;
} }
const cutscene_t * cutsceneCutsceneResolve(const char_t *name) { cutscene_t * cutsceneCutsceneResolve(const char_t *name) {
char_t path[ASSET_FILE_NAME_MAX]; char_t path[ASSET_FILE_NAME_MAX];
stringFormat(path, sizeof(path), "cutscenes/%s.jsonc", name); 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 // Steal the parsed doc before unlocking - assetJsonDispose would
// otherwise free it out from under CUTSCENE_LOADED_ITEMS' name pointers // otherwise free it out before it's been parsed below.
// the moment this entry gets reaped.
yyjson_doc *doc = entry->data.json; yyjson_doc *doc = entry->data.json;
entry->data.json = NULL; entry->data.json = NULL;
assetUnlockEntry(entry); assetUnlockEntry(entry);
@@ -421,12 +420,12 @@ const cutscene_t * cutsceneCutsceneResolve(const char_t *name) {
// trigger a reap - assetRequireLoaded treats an already-LOADED entry as // trigger a reap - assetRequireLoaded treats an already-LOADED entry as
// an instant cache hit with no reparsing, so a stale leftover entry for // 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 // this same path would otherwise make a later resolve of it wrongly
// skip loading, still returning CUTSCENE_LOADED_SCENE even after some // skip loading, still returning CUTSCENE_SYSTEM.loadedScene even after
// other cutscene has since overwritten it. // some other cutscene has since overwritten it.
assetReapUnused(); assetReapUnused();
errorret_t parseResult = cutsceneParseDoc( errorret_t parseResult = cutsceneParseDoc(
doc, &CUTSCENE_LOADED_SCENE, CUTSCENE_LOADED_ITEMS, doc, &CUTSCENE_SYSTEM.loadedScene, CUTSCENE_SYSTEM.loadedItems,
CUTSCENE_LOADED_ITEMS_MAX CUTSCENE_LOADED_ITEMS_MAX
); );
if(errorIsNotOk(parseResult)) { if(errorIsNotOk(parseResult)) {
@@ -437,7 +436,7 @@ const cutscene_t * cutsceneCutsceneResolve(const char_t *name) {
} }
cutsceneLoadedSetDoc(doc); cutsceneLoadedSetDoc(doc);
return &CUTSCENE_LOADED_SCENE; return &CUTSCENE_SYSTEM.loadedScene;
} }
errorret_t cutsceneCutsceneLoad( errorret_t cutsceneCutsceneLoad(
@@ -466,7 +465,7 @@ errorret_t cutsceneCutsceneRefLoad(
} }
outRef->cutscene = NULL; outRef->cutscene = NULL;
outRef->name = name; stringCopy(outRef->name, name, CUTSCENE_CUTSCENE_REF_NAME_MAX);
errorOk(); errorOk();
} }
@@ -475,16 +474,11 @@ void cutsceneInsertStart(
const cutsceneitem_t *item, const cutsceneitem_t *item,
cutsceneitemdata_t *data cutsceneitemdata_t *data
) { ) {
const cutscene_t *cutscene = item->insert.cutscene; if(item->insert.cutscene != NULL) {
if(cutscene == NULL && item->insert.name != NULL) { cutsceneSystemInsertCutscene(item->insert.cutscene);
// Not cached back into item - same reasoning as cutsceneCutsceneStart, } else if(item->insert.name[0] != '\0') {
// but resolved via cutsceneInsertResolve (the dedicated INSERT buffer) cutsceneInsertResolveAndSplice(item->insert.name);
// 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(cutscene != NULL) cutsceneSystemInsertCutscene(cutscene);
} }
bool_t cutsceneInsertUpdate( bool_t cutsceneInsertUpdate(
@@ -494,7 +488,7 @@ bool_t cutsceneInsertUpdate(
return false; return false;
} }
const cutscene_t * cutsceneInsertResolve(const char_t *name) { void cutsceneInsertResolveAndSplice(const char_t *name) {
char_t path[ASSET_FILE_NAME_MAX]; char_t path[ASSET_FILE_NAME_MAX];
stringFormat(path, sizeof(path), "cutscenes/%s.jsonc", name); stringFormat(path, sizeof(path), "cutscenes/%s.jsonc", name);
@@ -504,12 +498,11 @@ const cutscene_t * cutsceneInsertResolve(const char_t *name) {
assetUnlockEntry(entry); assetUnlockEntry(entry);
errorCatch(errorPrint(result)); errorCatch(errorPrint(result));
uiFatalErrorOpen(NULL); uiFatalErrorOpen(NULL);
return NULL; return;
} }
// Steal the parsed doc before unlocking - assetJsonDispose would // Steal the parsed doc before unlocking - assetJsonDispose would
// otherwise free it out from under CUTSCENE_INSERT_ITEMS' name pointers // otherwise free it out before it's been parsed below.
// the moment this entry gets reaped.
yyjson_doc *doc = entry->data.json; yyjson_doc *doc = entry->data.json;
entry->data.json = NULL; entry->data.json = NULL;
assetUnlockEntry(entry); assetUnlockEntry(entry);
@@ -518,19 +511,22 @@ const cutscene_t * cutsceneInsertResolve(const char_t *name) {
// never wrongly skips reloading it. // never wrongly skips reloading it.
assetReapUnused(); assetReapUnused();
errorret_t parseResult = cutsceneParseDoc( // Local scratch, not a shared buffer - this target is spliced straight
doc, &CUTSCENE_INSERT_SCENE, CUTSCENE_INSERT_ITEMS, // into the running scene below and never referenced again afterward, so
CUTSCENE_INSERT_ITEMS_MAX // 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)) { if(errorIsNotOk(parseResult)) {
yyjson_doc_free(doc);
errorCatch(errorPrint(parseResult)); errorCatch(errorPrint(parseResult));
uiFatalErrorOpen(NULL); uiFatalErrorOpen(NULL);
return NULL; return;
} }
cutsceneInsertSetDoc(doc); cutsceneSystemInsertCutscene(&scene);
return &CUTSCENE_INSERT_SCENE;
} }
errorret_t cutsceneInsertLoad( errorret_t cutsceneInsertLoad(
+60 -50
View File
@@ -8,6 +8,7 @@
#pragma once #pragma once
#include "error/error.h" #include "error/error.h"
#include "yyjson.h" #include "yyjson.h"
#include "asset/assetfile.h"
#include "cutsceneitembase.h" #include "cutsceneitembase.h"
#include "cutscenecallback.h" #include "cutscenecallback.h"
#include "cutsceneprint.h" #include "cutsceneprint.h"
@@ -57,28 +58,37 @@
typedef struct cutscene_s cutscene_t; typedef struct cutscene_s cutscene_t;
// A CUTSCENE (or INSERT) item's target. C-authored cutscenes (the // Max length of a CUTSCENE/INSERT item's "name" field - bounded by what
// CUTSCENE_CUTSCENE/CUTSCENE_INSERT macros in cutscene.h) resolve cutscene // fits in the "cutscenes/<name>.jsonc" path built from it (see
// at compile time and leave name NULL - safe to hold onto indefinitely, // cutsceneCutsceneRefLoad), so this is never reached in practice.
// since C-authored cutscenes are compiled directly into their own static #define CUTSCENE_CUTSCENE_REF_NAME_MAX ASSET_FILE_NAME_MAX
// const arrays, never a shared load buffer. JSON-authored cutscenes
// (cutsceneCutsceneLoad/cutsceneInsertLoad) do the opposite - only name is // A CUTSCENE (or INSERT) item's target. A C-authored cutscene_t* set
// set at parse time, since resolving/locking the referenced asset there // directly (e.g. by a test) leaves name empty ("") - safe to hold onto
// would eagerly load every cutscene a cutscene merely references, even // indefinitely, since C-authored cutscenes are compiled directly into
// ones never played. cutsceneCutsceneStart/cutsceneInsertStart resolve // their own static arrays, never a shared load buffer. JSON-authored
// name to cutscene the first time this item actually runs (via // cutscenes (cutsceneCutsceneLoad/cutsceneInsertLoad) do the opposite -
// cutsceneCutsceneResolve/cutsceneInsertResolve respectively) but do NOT // only name is set at parse time (copied by value out of the JSON doc,
// cache the result back into this field - with only one shared, fixed // so it stays valid regardless of the doc's own lifetime), since
// buffer per destination (see cutscenesystem.h), every JSON-authored // 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 // reference resolves to the same fixed address every time anyway, and
// item itself may live inside that very buffer, so caching a pointer into // item itself may live inside that very buffer, so caching a pointer
// it here would go stale (or, worse, get clobbered mid-resolve) the // into it here would go stale (or, worse, get clobbered mid-resolve) the
// moment a later load overwrites it. Shared by both CUTSCENE // moment a later load overwrites it. cutsceneInsertStart instead resolves
// (item->cutsceneRef) and INSERT (item->insert), since both need exactly // AND splices name in one step (cutsceneInsertResolveAndSplice) using a
// this "resolved pointer, or a name to lazily resolve" shape. // 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 { typedef struct {
const cutscene_t *cutscene; cutscene_t *cutscene;
const char_t *name; char_t name[CUTSCENE_CUTSCENE_REF_NAME_MAX];
} cutscenecutsceneref_t; } cutscenecutsceneref_t;
struct cutsceneitem_s { struct cutsceneitem_s {
@@ -215,20 +225,21 @@ void cutsceneCutsceneStart(
); );
/** /**
* Resolves a JSON-authored CUTSCENE item's referenced cutscene by name, * Resolves a JSON-authored CUTSCENE item's referenced cutscene by name:
* locking (forever - never unlocked, matching cutsceneCutsceneLoad's old * locks+loads assets/cutscenes/<name>.jsonc, parses it into
* convention) and blocking on assets/cutscenes/<name>.jsonc. Called * CUTSCENE_SYSTEM.loadedItems/loadedScene, then unlocks and reaps the asset entry
* lazily from cutsceneCutsceneStart the first time the item actually * immediately (see CUTSCENE_LOADED_ITEMS_MAX's doc comment for why).
* runs, not at parse time, so a cutscene referencing many others doesn't * Called lazily from cutsceneCutsceneStart the first time the item
* load every one of them up front. On failure, opens the fatal error * actually runs, not at parse time, so a cutscene referencing many others
* overlay (matching cutsceneSystemLoad's runtime-failure handling) and * doesn't load every one of them up front. On failure, opens the fatal
* returns NULL. * error overlay (matching cutsceneSystemLoad's runtime-failure handling)
* and returns NULL.
* *
* @param name Bare cutscene name (without "cutscenes/" prefix or * @param name Bare cutscene name (without "cutscenes/" prefix or
* ".jsonc" suffix). * ".jsonc" suffix).
* @return The resolved cutscene, or NULL on failure. * @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 * 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 * Starts an INSERT item, splicing the referenced cutscene's items into the
* running cutscene in place - unlike CUTSCENE (a one-way jump that replaces * running cutscene in place via cutsceneSystemInsertCutscene - unlike
* the running cutscene outright), control returns to the item right after * CUTSCENE (a one-way jump that replaces the running cutscene outright),
* this one once the inserted items run out. If item->insert.cutscene hasn't * whatever already followed this item continues normally once the
* been resolved yet (a JSON-authored reference, only known by name so * inserted items run out, just shifted further down the same array. If
* far), resolves it first via cutsceneInsertResolve - not * item->insert.cutscene is set (a compile-time target), splices it
* cutsceneCutsceneResolve, so the referenced cutscene lands in the * directly; otherwise (a JSON-authored reference, only known by name so
* dedicated CUTSCENE_INSERT_ITEMS buffer rather than overwriting * far) resolves and splices it via cutsceneInsertResolveAndSplice.
* 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.
* *
* @param item The cutscene item. * @param item The cutscene item.
* @param data Runtime data storage. * @param data Runtime data storage.
@@ -304,20 +311,23 @@ void cutsceneInsertStart(
); );
/** /**
* Resolves a JSON-authored INSERT item's referenced cutscene by name - * Resolves a JSON-authored INSERT item's referenced cutscene by name and
* locks+loads assets/cutscenes/<name>.jsonc (as a plain * splices it into the running cutscene (CUTSCENE_SYSTEM.scene): locks+
* ASSET_LOADER_TYPE_JSON asset, same path/key convention as * loads assets/cutscenes/<name>.jsonc as a plain ASSET_LOADER_TYPE_JSON
* cutsceneCutsceneResolve - which destination buffer it lands in is * asset, parses it into a small scratch buffer local to this call
* decided purely by which resolve function is called, not stored on the * (capacity CUTSCENE_INSERT_ITEMS_MAX), then immediately frees the parsed
* asset entry itself), then parses it into CUTSCENE_INSERT_ITEMS rather * doc - unlike cutsceneCutsceneResolve's CUTSCENE_SYSTEM.loadedScene, nothing
* than CUTSCENE_LOADED_ITEMS. On failure, opens the fatal error overlay * needs to persist past this call: cutsceneSystemInsertCutscene copies the
* and returns NULL. * 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 * @param name Bare cutscene name (without "cutscenes/" prefix or
* ".jsonc" suffix). * ".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 * 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/item/cutsceneitem.h"
#include "rpg/cutscene/cutscenesystem.h" #include "rpg/cutscene/cutscenesystem.h"
#include "rpg/cutscene/item/json/cutscenejsonfield.h"
#include "save/save.h" #include "save/save.h"
void cutsceneSaveDeviceCheckCallback(savedevice_t *device, void *user) { void cutsceneSaveDeviceCheckCallback(savedevice_t *device, void *user) {
@@ -36,18 +37,21 @@ errorret_t cutsceneSaveDeviceCheckLoad(
yyjson_val *itemObj, yyjson_val *itemObj,
cutsceneitem_t *item cutsceneitem_t *item
) { ) {
const char_t *successMarker = if(
yyjson_get_str(yyjson_obj_get(itemObj, "successMarker")); !cutsceneJsonCopyString(
const char_t *failureMarker = itemObj, "successMarker", NULL, item->saveDeviceCheck.successMarker,
yyjson_get_str(yyjson_obj_get(itemObj, "failureMarker")); CUTSCENE_MARKER_NAME_MAX
if(successMarker == NULL || failureMarker == NULL) { ) ||
!cutsceneJsonCopyString(
itemObj, "failureMarker", NULL, item->saveDeviceCheck.failureMarker,
CUTSCENE_MARKER_NAME_MAX
)
) {
errorThrow( errorThrow(
"Cutscene item (SAVE_DEVICE_CHECK): needs 'successMarker'/" "Cutscene item (SAVE_DEVICE_CHECK): needs 'successMarker'/"
"'failureMarker'" "'failureMarker'"
); );
} }
item->saveDeviceCheck.successMarker = successMarker;
item->saveDeviceCheck.failureMarker = failureMarker;
errorOk(); errorOk();
} }
@@ -7,10 +7,11 @@
#pragma once #pragma once
#include "rpg/cutscene/item/cutsceneitembase.h" #include "rpg/cutscene/item/cutsceneitembase.h"
#include "rpg/cutscene/item/control/cutscenemarker.h"
typedef struct { typedef struct {
const char_t *successMarker; char_t successMarker[CUTSCENE_MARKER_NAME_MAX];
const char_t *failureMarker; char_t failureMarker[CUTSCENE_MARKER_NAME_MAX];
} cutscenesavedevicecheck_t; } cutscenesavedevicecheck_t;
/** /**
@@ -7,6 +7,7 @@
#include "rpg/cutscene/item/cutsceneitem.h" #include "rpg/cutscene/item/cutsceneitem.h"
#include "rpg/cutscene/cutscenesystem.h" #include "rpg/cutscene/cutscenesystem.h"
#include "rpg/cutscene/item/json/cutscenejsonfield.h"
#include "save/save.h" #include "save/save.h"
void cutsceneSaveLoadAllSlotsStart( void cutsceneSaveLoadAllSlotsStart(
@@ -34,18 +35,21 @@ errorret_t cutsceneSaveLoadAllSlotsLoad(
yyjson_val *itemObj, yyjson_val *itemObj,
cutsceneitem_t *item cutsceneitem_t *item
) { ) {
const char_t *successMarker = if(
yyjson_get_str(yyjson_obj_get(itemObj, "successMarker")); !cutsceneJsonCopyString(
const char_t *failureMarker = itemObj, "successMarker", NULL, item->saveLoadAllSlots.successMarker,
yyjson_get_str(yyjson_obj_get(itemObj, "failureMarker")); CUTSCENE_MARKER_NAME_MAX
if(successMarker == NULL || failureMarker == NULL) { ) ||
!cutsceneJsonCopyString(
itemObj, "failureMarker", NULL, item->saveLoadAllSlots.failureMarker,
CUTSCENE_MARKER_NAME_MAX
)
) {
errorThrow( errorThrow(
"Cutscene item (SAVE_LOAD_ALL_SLOTS): needs 'successMarker'/" "Cutscene item (SAVE_LOAD_ALL_SLOTS): needs 'successMarker'/"
"'failureMarker'" "'failureMarker'"
); );
} }
item->saveLoadAllSlots.successMarker = successMarker;
item->saveLoadAllSlots.failureMarker = failureMarker;
errorOk(); errorOk();
} }
@@ -7,10 +7,11 @@
#pragma once #pragma once
#include "rpg/cutscene/item/cutsceneitembase.h" #include "rpg/cutscene/item/cutsceneitembase.h"
#include "rpg/cutscene/item/control/cutscenemarker.h"
typedef struct { typedef struct {
const char_t *successMarker; char_t successMarker[CUTSCENE_MARKER_NAME_MAX];
const char_t *failureMarker; char_t failureMarker[CUTSCENE_MARKER_NAME_MAX];
} cutscenesaveloadallslots_t; } cutscenesaveloadallslots_t;
/** /**
@@ -32,10 +32,18 @@ void cutsceneModalOptionsMarkersStart(
) { ) {
const cutscenemodaloptionsmarkers_t *options = &item->modalOptionsMarkers; 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( uiModalOpen(
options->title, options->title,
options->message, options->message,
options->options, optionPtrs,
options->optionCount, options->optionCount,
cutsceneModalOptionsMarkersCallback, cutsceneModalOptionsMarkersCallback,
NULL, NULL,
@@ -82,17 +90,21 @@ errorret_t cutsceneModalOptionsMarkersLoad(
yyjson_val *optionVal; yyjson_val *optionVal;
size_t o = 0; size_t o = 0;
while((optionVal = yyjson_arr_iter_next(&iter)) != NULL) { while((optionVal = yyjson_arr_iter_next(&iter)) != NULL) {
const char_t *text = yyjson_get_str(yyjson_obj_get(optionVal, "text")); if(
const char_t *marker = !cutsceneJsonCopyString(
yyjson_get_str(yyjson_obj_get(optionVal, "marker")); optionVal, "text", NULL, item->modalOptionsMarkers.options[o],
if(text == NULL || marker == NULL) { UI_MODAL_OPTION_TEXT_MAX
) ||
!cutsceneJsonCopyString(
optionVal, "marker", NULL, item->modalOptionsMarkers.markers[o],
CUTSCENE_MARKER_NAME_MAX
)
) {
errorThrow( errorThrow(
"Cutscene item (MODAL_OPTIONS_MARKERS): options[%u] needs 'text' " "Cutscene item (MODAL_OPTIONS_MARKERS): options[%u] needs "
"and 'marker'", (uint32_t)o "'text'/'marker'", (uint32_t)o
); );
} }
item->modalOptionsMarkers.options[o] = text;
item->modalOptionsMarkers.markers[o] = marker;
o++; o++;
} }
@@ -8,9 +8,11 @@
#pragma once #pragma once
#include "rpg/cutscene/item/cutsceneitembase.h" #include "rpg/cutscene/item/cutsceneitembase.h"
#include "rpg/cutscene/item/ui/cutscenemodal.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 // Two options is the most either JSON MODAL_OPTIONS_MARKERS item ever
// is the most either macro ever needs. // needs.
#define CUTSCENE_MODAL_OPTIONS_MARKERS_MAX 2 #define CUTSCENE_MODAL_OPTIONS_MARKERS_MAX 2
typedef struct { typedef struct {
@@ -20,9 +22,11 @@ typedef struct {
char_t message[CUTSCENE_MODAL_MESSAGE_MAX_CHARS]; char_t message[CUTSCENE_MODAL_MESSAGE_MAX_CHARS];
// Each option's display text/locale message ID, and the marker to // Each option's display text/locale message ID, and the marker to
// cutsceneGoTo when that option is selected. // cutsceneGoTo when that option is selected. Copied by value at parse
const char_t *options[CUTSCENE_MODAL_OPTIONS_MARKERS_MAX]; // time (see cutsceneModalOptionsMarkersLoad), so they stay valid
const char_t *markers[CUTSCENE_MODAL_OPTIONS_MARKERS_MAX]; // 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; uint8_t optionCount;
} cutscenemodaloptionsmarkers_t; } cutscenemodaloptionsmarkers_t;
@@ -28,7 +28,7 @@ void entityInteractWith(entity_t *player, entity_t *target) {
break; break;
case ENTITY_INTERACT_CUTSCENE_NAME: { case ENTITY_INTERACT_CUTSCENE_NAME: {
const cutscene_t *cutscene = cutscene_t *cutscene =
cutsceneCutsceneResolve(target->interact.data.cutsceneName); cutsceneCutsceneResolve(target->interact.data.cutsceneName);
if(cutscene != NULL) { if(cutscene != NULL) {
cutsceneSystemStartCutsceneWith(cutscene, player, target); cutsceneSystemStartCutsceneWith(cutscene, player, target);
@@ -31,9 +31,10 @@ typedef enum {
typedef union { typedef union {
// A compile-time cutscene_t* (a C-authored, test-only cutscene) - safe // A compile-time cutscene_t* (a C-authored, test-only cutscene) - safe
// to hold onto indefinitely, since it's compiled directly into its own // to hold onto indefinitely, since it's compiled directly into its own
// static const arrays, never the shared CUTSCENE_LOADED_ITEMS buffer // static arrays, never the shared CUTSCENE_SYSTEM.loadedItems buffer (see
// (see cutscenesystem.h). // cutscenesystem.h). Non-const since cutsceneSystemStartCutsceneWith may
const cutscene_t *cutscene; // splice CUTSCENE_ITEM_TYPE_INSERT items into it at runtime.
cutscene_t *cutscene;
// A bare cutscene name (as passed to cutsceneCutsceneResolve), resolved // A bare cutscene name (as passed to cutsceneCutsceneResolve), resolved
// lazily at interact time rather than once up front - only one // lazily at interact time rather than once up front - only one
// JSON-authored cutscene is ever resident at a time (see // JSON-authored cutscene is ever resident at a time (see
+4 -4
View File
@@ -27,7 +27,7 @@
uimainmenu_t UI_MAIN_MENU; uimainmenu_t UI_MAIN_MENU;
const cutscene_t *uiMainMenuLoadCutscene(void) { cutscene_t *uiMainMenuLoadCutscene(void) {
// Delegates entirely to cutsceneCutsceneResolve rather than keeping our // Delegates entirely to cutsceneCutsceneResolve rather than keeping our
// own locked-forever entry, since only one JSON-authored cutscene is // own locked-forever entry, since only one JSON-authored cutscene is
// ever resident at a time (see CUTSCENE_LOADED_ITEMS_MAX's doc comment // ever resident at a time (see CUTSCENE_LOADED_ITEMS_MAX's doc comment
@@ -73,7 +73,7 @@ void uiMainMenuOpenSelectSave(void *userData) {
void uiMainMenuStartGame(void) { void uiMainMenuStartGame(void) {
if(CUTSCENE_SYSTEM.scene == NULL) { if(CUTSCENE_SYSTEM.scene == NULL) {
const cutscene_t *cutscene = uiMainMenuLoadCutscene(); cutscene_t *cutscene = uiMainMenuLoadCutscene();
if(cutscene == NULL) return; if(cutscene == NULL) return;
cutsceneSystemStartCutsceneAndGoToMarker(cutscene, "NEW_GAME"); cutsceneSystemStartCutsceneAndGoToMarker(cutscene, "NEW_GAME");
} else { } else {
@@ -85,7 +85,7 @@ void uiMainMenuStartGame(void) {
void uiMainMenuOptions(void) { void uiMainMenuOptions(void) {
if(CUTSCENE_SYSTEM.scene == NULL) { if(CUTSCENE_SYSTEM.scene == NULL) {
const cutscene_t *cutscene = uiMainMenuLoadCutscene(); cutscene_t *cutscene = uiMainMenuLoadCutscene();
if(cutscene == NULL) return; if(cutscene == NULL) return;
cutsceneSystemStartCutsceneAndGoToMarker(cutscene, "OPTIONS"); cutsceneSystemStartCutsceneAndGoToMarker(cutscene, "OPTIONS");
} else { } else {
@@ -95,7 +95,7 @@ void uiMainMenuOptions(void) {
void uiMainMenuQuit(void) { void uiMainMenuQuit(void) {
if(CUTSCENE_SYSTEM.scene == NULL) { if(CUTSCENE_SYSTEM.scene == NULL) {
const cutscene_t *cutscene = uiMainMenuLoadCutscene(); cutscene_t *cutscene = uiMainMenuLoadCutscene();
if(cutscene == NULL) return; if(cutscene == NULL) return;
cutsceneSystemStartCutsceneAndGoToMarker(cutscene, "QUIT"); cutsceneSystemStartCutsceneAndGoToMarker(cutscene, "QUIT");
} else { } else {
+1 -1
View File
@@ -50,7 +50,7 @@ void uiMainMenuOpen(void);
* @return The main menu cutscene, or NULL on failure (already reported via * @return The main menu cutscene, or NULL on failure (already reported via
* uiFatalErrorOpen). * uiFatalErrorOpen).
*/ */
const cutscene_t *uiMainMenuLoadCutscene(void); cutscene_t *uiMainMenuLoadCutscene(void);
/** /**
* Starts the "Start Game" flow: jumps the already-running main menu * Starts the "Start Game" flow: jumps the already-running main menu
+2 -1
View File
@@ -27,9 +27,10 @@ static cutsceneitem_t CUTSCENE_TEST_SCRATCH_ITEMS[] = {
}, },
{ .type = CUTSCENE_ITEM_TYPE_SET_PAUSE, .setPause = CUTSCENE_PAUSE_NONE } { .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, .items = CUTSCENE_TEST_SCRATCH_ITEMS,
.itemCount = sizeof(CUTSCENE_TEST_SCRATCH_ITEMS) / sizeof(cutsceneitem_t), .itemCount = sizeof(CUTSCENE_TEST_SCRATCH_ITEMS) / sizeof(cutsceneitem_t),
.itemsMax = sizeof(CUTSCENE_TEST_SCRATCH_ITEMS) / sizeof(cutsceneitem_t),
.pause = CUTSCENE_PAUSE_NONE .pause = CUTSCENE_PAUSE_NONE
}; };
+104 -120
View File
@@ -23,9 +23,10 @@ static void recordCallback(void *userData) {
static cutsceneitem_t CUTSCENE_TEST_INNER_ITEMS[] = { static cutsceneitem_t CUTSCENE_TEST_INNER_ITEMS[] = {
{ .type = CUTSCENE_ITEM_TYPE_CALLBACK, .callback = recordCallback } { .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, .items = CUTSCENE_TEST_INNER_ITEMS,
.itemCount = sizeof(CUTSCENE_TEST_INNER_ITEMS) / sizeof(cutsceneitem_t), .itemCount = sizeof(CUTSCENE_TEST_INNER_ITEMS) / sizeof(cutsceneitem_t),
.itemsMax = sizeof(CUTSCENE_TEST_INNER_ITEMS) / sizeof(cutsceneitem_t),
.pause = CUTSCENE_PAUSE_NONE .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_WAIT, .wait = 0.5f },
{ {
.type = CUTSCENE_ITEM_TYPE_CUTSCENE, .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, .items = CUTSCENE_TEST_OUTER_ITEMS,
.itemCount = sizeof(CUTSCENE_TEST_OUTER_ITEMS) / sizeof(cutsceneitem_t), .itemCount = sizeof(CUTSCENE_TEST_OUTER_ITEMS) / sizeof(cutsceneitem_t),
.itemsMax = sizeof(CUTSCENE_TEST_OUTER_ITEMS) / sizeof(cutsceneitem_t),
.pause = CUTSCENE_PAUSE_DEFAULT .pause = CUTSCENE_PAUSE_DEFAULT
}; };
@@ -91,9 +93,10 @@ static void test_cutsceneSystemUpdateIsNoopWithNoActiveCutscene(void **state) {
static cutsceneitem_t CUTSCENE_TEST_SINGLE_WAIT_ITEMS[] = { static cutsceneitem_t CUTSCENE_TEST_SINGLE_WAIT_ITEMS[] = {
{ .type = CUTSCENE_ITEM_TYPE_WAIT, .wait = 1.0f } { .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, .items = CUTSCENE_TEST_SINGLE_WAIT_ITEMS,
.itemCount = sizeof(CUTSCENE_TEST_SINGLE_WAIT_ITEMS) / sizeof(cutsceneitem_t), .itemCount = sizeof(CUTSCENE_TEST_SINGLE_WAIT_ITEMS) / sizeof(cutsceneitem_t),
.itemsMax = sizeof(CUTSCENE_TEST_SINGLE_WAIT_ITEMS) / sizeof(cutsceneitem_t),
.pause = CUTSCENE_PAUSE_NONE .pause = CUTSCENE_PAUSE_NONE
}; };
@@ -196,6 +199,17 @@ static void test_cutsceneSystemDisposeResetsState(void **state) {
} }
// --- CUTSCENE_INSERT --------------------------------------------------- // --- 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 insertOrderLog[8];
static uint8_t insertOrderLogCount; 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_WAIT, .wait = 2.0f },
{ .type = CUTSCENE_ITEM_TYPE_CALLBACK, .callback = recordOrderB } { .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, .items = CUTSCENE_TEST_INSERT_SNIPPET_ITEMS,
.itemCount = .itemCount =
sizeof(CUTSCENE_TEST_INSERT_SNIPPET_ITEMS) / sizeof(cutsceneitem_t), sizeof(CUTSCENE_TEST_INSERT_SNIPPET_ITEMS) / sizeof(cutsceneitem_t),
.itemsMax =
sizeof(CUTSCENE_TEST_INSERT_SNIPPET_ITEMS) / sizeof(cutsceneitem_t),
.pause = CUTSCENE_PAUSE_NONE .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_CALLBACK, .callback = recordOrder0 },
{ .type = CUTSCENE_ITEM_TYPE_WAIT, .wait = 1.0f }, { .type = CUTSCENE_ITEM_TYPE_WAIT, .wait = 1.0f },
{ .type = CUTSCENE_ITEM_TYPE_CALLBACK, .callback = recordOrder1 }, { .type = CUTSCENE_ITEM_TYPE_CALLBACK, .callback = recordOrder1 },
{ {
.type = CUTSCENE_ITEM_TYPE_INSERT, .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 } { .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, .items = CUTSCENE_TEST_INSERT_OUTER_ITEMS,
.itemCount = .itemCount = 5,
sizeof(CUTSCENE_TEST_INSERT_OUTER_ITEMS) / sizeof(cutsceneitem_t), .itemsMax = sizeof(CUTSCENE_TEST_INSERT_OUTER_ITEMS) / sizeof(cutsceneitem_t),
.pause = CUTSCENE_PAUSE_NONE .pause = CUTSCENE_PAUSE_NONE
}; };
@@ -246,12 +263,15 @@ static void test_cutsceneInsertSplicesItemsInPlaceThenResumes(void **state) {
cutsceneSystemUpdate();// the WAIT elapses -> starts order1 cutsceneSystemUpdate();// the WAIT elapses -> starts order1
cutsceneSystemUpdate();// order1 done -> starts the INSERT, cascading into orderA 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) TIME.delta = 2.5f;// elapses the snippet's WAIT(2.0)
cutsceneSystemUpdate();// orderA done -> starts snippet's WAIT cutsceneSystemUpdate();// orderA done -> starts snippet's WAIT
cutsceneSystemUpdate();// snippet's WAIT elapses -> starts orderB 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); 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[] = { static cutsceneitem_t CUTSCENE_TEST_INSERT_AS_LAST_ITEM_SNIPPET_ITEMS[] = {
{ .type = CUTSCENE_ITEM_TYPE_CALLBACK, .callback = recordOrderA } { .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, .items = CUTSCENE_TEST_INSERT_AS_LAST_ITEM_SNIPPET_ITEMS,
.itemCount = sizeof(CUTSCENE_TEST_INSERT_AS_LAST_ITEM_SNIPPET_ITEMS) / .itemCount = sizeof(CUTSCENE_TEST_INSERT_AS_LAST_ITEM_SNIPPET_ITEMS) /
sizeof(cutsceneitem_t), sizeof(cutsceneitem_t),
.itemsMax = sizeof(CUTSCENE_TEST_INSERT_AS_LAST_ITEM_SNIPPET_ITEMS) /
sizeof(cutsceneitem_t),
.pause = CUTSCENE_PAUSE_NONE .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_CALLBACK, .callback = recordOrder0 },
{ {
.type = CUTSCENE_ITEM_TYPE_INSERT, .type = CUTSCENE_ITEM_TYPE_INSERT,
.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, .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), sizeof(cutsceneitem_t),
.pause = CUTSCENE_PAUSE_NONE .pause = CUTSCENE_PAUSE_NONE
}; };
@@ -298,12 +322,11 @@ static void test_cutsceneInsertAsLastItemEndsCutsceneNaturally(
cutsceneSystemStartCutscene(&CUTSCENE_TEST_INSERT_AS_LAST_ITEM_OUTER); cutsceneSystemStartCutscene(&CUTSCENE_TEST_INSERT_AS_LAST_ITEM_OUTER);
cutsceneSystemUpdate();// order0 done -> starts INSERT, cascades to orderA cutsceneSystemUpdate();// order0 done -> starts INSERT, cascades to orderA
assert_ptr_equal( assert_ptr_equal(CUTSCENE_SYSTEM.scene, &CUTSCENE_TEST_INSERT_AS_LAST_ITEM_OUTER);
CUTSCENE_SYSTEM.scene, &CUTSCENE_TEST_INSERT_AS_LAST_ITEM_SNIPPET assert_int_equal(CUTSCENE_SYSTEM.scene->itemCount, 3);
);
// orderA is the snippet's only item, and the snippet is the outer's last // orderA is the snippet's only item, and the snippet was spliced in as
// item -- finishing it should pop straight through to the natural end // the outer's last item -- finishing it should reach the natural end
// of the whole cutscene, not get stuck. // of the whole cutscene, not get stuck.
cutsceneSystemUpdate(); cutsceneSystemUpdate();
assert_null(CUTSCENE_SYSTEM.scene); assert_null(CUTSCENE_SYSTEM.scene);
@@ -316,42 +339,51 @@ static void test_cutsceneInsertAsLastItemEndsCutsceneNaturally(
static cutsceneitem_t CUTSCENE_TEST_INSERT_NESTED_INNER_ITEMS[] = { static cutsceneitem_t CUTSCENE_TEST_INSERT_NESTED_INNER_ITEMS[] = {
{ .type = CUTSCENE_ITEM_TYPE_CALLBACK, .callback = recordOrderA } { .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, .items = CUTSCENE_TEST_INSERT_NESTED_INNER_ITEMS,
.itemCount = .itemCount =
sizeof(CUTSCENE_TEST_INSERT_NESTED_INNER_ITEMS) / sizeof(cutsceneitem_t), 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 .pause = CUTSCENE_PAUSE_NONE
}; };
// INSERT is MID's first item, so entering MID (from OUTER) cascades // INSERT is MID's first item, so entering MID (from OUTER) cascades
// straight through into INNER within the same Start chain, one level // 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[] = { static cutsceneitem_t CUTSCENE_TEST_INSERT_NESTED_MID_ITEMS[] = {
{ {
.type = CUTSCENE_ITEM_TYPE_INSERT, .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 } { .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, .items = CUTSCENE_TEST_INSERT_NESTED_MID_ITEMS,
.itemCount = .itemCount =
sizeof(CUTSCENE_TEST_INSERT_NESTED_MID_ITEMS) / sizeof(cutsceneitem_t), 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 .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_CALLBACK, .callback = recordOrder0 },
{ {
.type = CUTSCENE_ITEM_TYPE_INSERT, .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 } { .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, .items = CUTSCENE_TEST_INSERT_NESTED_OUTER_ITEMS,
.itemCount = .itemCount = 3,
sizeof(CUTSCENE_TEST_INSERT_NESTED_OUTER_ITEMS) / sizeof(cutsceneitem_t), .itemsMax = sizeof(CUTSCENE_TEST_INSERT_NESTED_OUTER_ITEMS) /
sizeof(cutsceneitem_t),
.pause = CUTSCENE_PAUSE_NONE .pause = CUTSCENE_PAUSE_NONE
}; };
@@ -362,16 +394,14 @@ static void test_cutsceneInsertNestsThroughMultipleLevels(void **state) {
cutsceneSystemStartCutscene(&CUTSCENE_TEST_INSERT_NESTED_OUTER); cutsceneSystemStartCutscene(&CUTSCENE_TEST_INSERT_NESTED_OUTER);
// order0 done -> starts INSERT(MID), cascading through INSERT(INNER) -> // 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(); 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_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 cutsceneSystemUpdate();// order2 done -> ends naturally
assert_null(CUTSCENE_SYSTEM.scene); assert_null(CUTSCENE_SYSTEM.scene);
@@ -380,87 +410,37 @@ static void test_cutsceneInsertNestsThroughMultipleLevels(void **state) {
assert_memory_equal(insertOrderLog, expected, sizeof(expected)); 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 } { .type = CUTSCENE_ITEM_TYPE_CALLBACK, .callback = recordOrderA }
}; };
static const cutscene_t CUTSCENE_TEST_INSERT_OVERFLOW_LEVEL5 = { static cutscene_t CUTSCENE_TEST_INSERT_NO_CAPACITY_SNIPPET = {
.items = CUTSCENE_TEST_INSERT_OVERFLOW_LEVEL5_ITEMS, .items = CUTSCENE_TEST_INSERT_NO_CAPACITY_SNIPPET_ITEMS,
.itemCount = sizeof(CUTSCENE_TEST_INSERT_OVERFLOW_LEVEL5_ITEMS) / .itemCount = 1,
sizeof(cutsceneitem_t), .itemsMax = 1,
.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),
.pause = CUTSCENE_PAUSE_NONE .pause = CUTSCENE_PAUSE_NONE
}; };
static void test_cutsceneInsertAssertsWhenNestingExceedsStackMax( // No spare capacity at all - itemsMax equals itemCount, leaving no room
void **state // 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(); 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( 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[] = { static cutsceneitem_t CUTSCENE_TEST_INSERT_PERSIST_SNIPPET_ITEMS[] = {
{ .type = CUTSCENE_ITEM_TYPE_CALLBACK, .callback = noopInsertCallback } { .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, .items = CUTSCENE_TEST_INSERT_PERSIST_SNIPPET_ITEMS,
.itemCount = sizeof(CUTSCENE_TEST_INSERT_PERSIST_SNIPPET_ITEMS) / .itemCount = sizeof(CUTSCENE_TEST_INSERT_PERSIST_SNIPPET_ITEMS) /
sizeof(cutsceneitem_t), sizeof(cutsceneitem_t),
.itemsMax = sizeof(CUTSCENE_TEST_INSERT_PERSIST_SNIPPET_ITEMS) /
sizeof(cutsceneitem_t),
.pause = CUTSCENE_PAUSE_ALL .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, .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, .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), sizeof(cutsceneitem_t),
.pause = CUTSCENE_PAUSE_NPC .pause = CUTSCENE_PAUSE_NPC
}; };
@@ -501,10 +485,10 @@ static void test_cutsceneInsertDoesNotResetPauseOrInteractEntities(
&CUTSCENE_TEST_INSERT_PERSIST_OUTER, &ENTITIES[3], &ENTITIES[4] &CUTSCENE_TEST_INSERT_PERSIST_OUTER, &ENTITIES[3], &ENTITIES[4]
); );
// The INSERT item's Start already cascaded into the snippet by now. // The INSERT item's Start already spliced the snippet into place and
assert_ptr_equal( // cascaded into it by now.
CUTSCENE_SYSTEM.scene, &CUTSCENE_TEST_INSERT_PERSIST_SNIPPET 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.entityInteract, &ENTITIES[3]);
assert_ptr_equal(CUTSCENE_SYSTEM.entityInteracted, &ENTITIES[4]); assert_ptr_equal(CUTSCENE_SYSTEM.entityInteracted, &ENTITIES[4]);
// The snippet declares ALL, but INSERT doesn't apply it -- the outer // 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_cutsceneInsertSplicesItemsInPlaceThenResumes),
cmocka_unit_test(test_cutsceneInsertAsLastItemEndsCutsceneNaturally), cmocka_unit_test(test_cutsceneInsertAsLastItemEndsCutsceneNaturally),
cmocka_unit_test(test_cutsceneInsertNestsThroughMultipleLevels), cmocka_unit_test(test_cutsceneInsertNestsThroughMultipleLevels),
cmocka_unit_test(test_cutsceneInsertAssertsWhenNestingExceedsStackMax), cmocka_unit_test(test_cutsceneInsertAssertsWhenExceedingItemsMax),
cmocka_unit_test(test_cutsceneInsertDoesNotResetPauseOrInteractEntities), cmocka_unit_test(test_cutsceneInsertDoesNotResetPauseOrInteractEntities),
}; };
+2 -1
View File
@@ -14,9 +14,10 @@
static cutsceneitem_t CUTSCENE_TEST_INTERACT_ITEMS[] = { static cutsceneitem_t CUTSCENE_TEST_INTERACT_ITEMS[] = {
{ .type = CUTSCENE_ITEM_TYPE_WAIT, .wait = 1.0f } { .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, .items = CUTSCENE_TEST_INTERACT_ITEMS,
.itemCount = 1, .itemCount = 1,
.itemsMax = 1,
.pause = CUTSCENE_PAUSE_NONE .pause = CUTSCENE_PAUSE_NONE
}; };