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:
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
);
|
||||
|
||||
@@ -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];
|
||||
}
|
||||
|
||||
@@ -10,100 +10,26 @@
|
||||
|
||||
typedef struct entity_s entity_t;
|
||||
|
||||
#define CUTSCENE_ENTITY_INTERACT ((uint8_t)0xFE)
|
||||
#define CUTSCENE_ENTITY_INTERACTED ((uint8_t)0xFD)
|
||||
#define CUTSCENE_ENTITY_LAST_CREATED ((uint8_t)0xFC)
|
||||
#define CUTSCENE_ENTITY_LAST_REF ((uint8_t)0xFB)
|
||||
#define CUTSCENE_AREA_LAST_CREATED ((uint8_t)0xFF)
|
||||
// Sentinels accepted in place of a literal index/ID by the cutsceneSystemGet*
|
||||
// resolvers below.
|
||||
#define CUTSCENE_ENTITY_INTERACT ((uint8_t)0xFE)
|
||||
#define CUTSCENE_ENTITY_INTERACTED ((uint8_t)0xFD)
|
||||
#define CUTSCENE_ENTITY_LAST_CREATED ((uint8_t)0xFC)
|
||||
#define CUTSCENE_ENTITY_LAST_REF ((uint8_t)0xFB)
|
||||
#define CUTSCENE_AREA_LAST_CREATED ((uint8_t)0xFF)
|
||||
#define CUTSCENE_TEXT_MINI_LAST_CREATED ((uint8_t)0xFA)
|
||||
|
||||
// Size in bytes of cutscenesystem_t.userData, the running cutscene's
|
||||
// scratch space.
|
||||
#define CUTSCENE_SYSTEM_SIZE_MAX 8192
|
||||
|
||||
// Size of CUTSCENE_SYSTEM.textCache - keep >= UI_KEYBOARD_TEXT_MAX (see
|
||||
// ui/dialog/keyboard/uikeyboard.h) so text entered via a
|
||||
// CUTSCENE_ITEM_TYPE_KEYBOARD item is never truncated caching it here.
|
||||
#define CUTSCENE_TEXT_CACHE_MAX 64
|
||||
|
||||
// Only one JSON-authored cutscene is ever loaded/playing at a time - a
|
||||
// CUTSCENE item hands control off completely (see cutsceneCutsceneStart)
|
||||
// and cutsceneSystemLoad/cutsceneCutsceneResolve release their asset lock
|
||||
// the instant loading finishes - so there's no benefit to malloc'ing a
|
||||
// fresh items buffer per load. Instead those functions parse directly
|
||||
// into this fixed, static buffer via cutsceneParseDoc (see cutscene.h),
|
||||
// once the raw JSON itself has been read+parsed via the generic
|
||||
// ASSET_LOADER_TYPE_JSON asset loader. Bump this if a real cutscene ever
|
||||
// needs more; C-authored cutscenes (used only by tests) don't use this
|
||||
// at all - they're compiled directly into their own static const arrays.
|
||||
#define CUTSCENE_LOADED_ITEMS_MAX 128
|
||||
|
||||
extern cutsceneitem_t CUTSCENE_LOADED_ITEMS[CUTSCENE_LOADED_ITEMS_MAX];
|
||||
|
||||
// The cutscene_t for whatever was last parsed into CUTSCENE_LOADED_ITEMS -
|
||||
// .items always points at CUTSCENE_LOADED_ITEMS; itemCount/pause are
|
||||
// overwritten by the loader on each load.
|
||||
extern cutscene_t CUTSCENE_LOADED_SCENE;
|
||||
|
||||
/**
|
||||
* Frees whatever yyjson_doc currently backs CUTSCENE_LOADED_ITEMS' string
|
||||
* fields (marker/cutscene-reference names point directly into it - see
|
||||
* cutsceneCutsceneRefLoad/cutsceneMarkerLoad) and takes ownership of doc
|
||||
* instead. Called by cutsceneSystemLoad/cutsceneCutsceneResolve once a
|
||||
* fresh parse into CUTSCENE_LOADED_ITEMS completes successfully, and from
|
||||
* cutsceneSystemDispose to release the last one at shutdown.
|
||||
*
|
||||
* @param doc The new doc to take ownership of (NULL just frees/clears the
|
||||
* current one).
|
||||
*/
|
||||
void cutsceneLoadedSetDoc(yyjson_doc *doc);
|
||||
|
||||
// A second, much smaller buffer dedicated to CUTSCENE_ITEM_TYPE_INSERT
|
||||
// (see cutsceneSystemInsertCutscene) - kept entirely separate from
|
||||
// CUTSCENE_LOADED_ITEMS so that a JSON-authored INSERT item resolving its
|
||||
// target by name (via cutsceneInsertResolve) never overwrites the buffer
|
||||
// the currently-running outer cutscene's own items live in. Sized small
|
||||
// deliberately - an inserted snippet is meant to be a short splice, not a
|
||||
// full cutscene.
|
||||
// Scratch capacity for resolving a JSON-authored CUTSCENE_ITEM_TYPE_INSERT
|
||||
// target - see cutsceneSystemInsertCutscene. Sized small deliberately -
|
||||
// an inserted snippet is meant to be a short splice, not a full cutscene.
|
||||
#define CUTSCENE_INSERT_ITEMS_MAX 16
|
||||
|
||||
extern cutsceneitem_t CUTSCENE_INSERT_ITEMS[CUTSCENE_INSERT_ITEMS_MAX];
|
||||
|
||||
// The cutscene_t for whatever was last parsed into CUTSCENE_INSERT_ITEMS -
|
||||
// .items always points at CUTSCENE_INSERT_ITEMS. Only one JSON-authored
|
||||
// INSERT target is ever resident at a time, same restriction as
|
||||
// CUTSCENE_LOADED_SCENE - a JSON-authored INSERT whose target itself
|
||||
// contains a JSON-authored INSERT would overwrite this out from under
|
||||
// itself, so that isn't supported (compile-time CUTSCENE_INSERT nesting is
|
||||
// unaffected, since it never touches asset loading).
|
||||
extern cutscene_t CUTSCENE_INSERT_SCENE;
|
||||
|
||||
/**
|
||||
* Same as cutsceneLoadedSetDoc, but for whatever yyjson_doc currently
|
||||
* backs CUTSCENE_INSERT_ITEMS' string fields. Called by
|
||||
* cutsceneInsertResolve once a fresh parse into CUTSCENE_INSERT_ITEMS
|
||||
* completes successfully, and from cutsceneSystemDispose to release the
|
||||
* last one at shutdown.
|
||||
*
|
||||
* @param doc The new doc to take ownership of (NULL just frees/clears the
|
||||
* current one).
|
||||
*/
|
||||
void cutsceneInsertSetDoc(yyjson_doc *doc);
|
||||
|
||||
// Maximum nesting depth of CUTSCENE_ITEM_TYPE_INSERT items - how many
|
||||
// "return to the item after this one" frames cutsceneSystemInsertCutscene
|
||||
// may have pushed at once (see CUTSCENE_SYSTEM.insertStack).
|
||||
#define CUTSCENE_INSERT_STACK_MAX 4
|
||||
|
||||
// One "return address" pushed by cutsceneSystemInsertCutscene - the scene
|
||||
// and item index to resume at once the inserted cutscene's items run out.
|
||||
typedef struct {
|
||||
const cutscene_t *scene;
|
||||
uint8_t currentItem;
|
||||
} cutsceneinsertframe_t;
|
||||
|
||||
typedef struct {
|
||||
const cutscene_t *scene;
|
||||
cutscene_t *scene;
|
||||
uint8_t currentItem;
|
||||
cutscenepause_t pause;
|
||||
entity_t *entityInteract;
|
||||
@@ -119,7 +45,7 @@ typedef struct {
|
||||
// may read or write it.
|
||||
char_t textCache[CUTSCENE_TEXT_CACHE_MAX];
|
||||
|
||||
// Data (used by the current item).
|
||||
// Runtime data for the current item.
|
||||
cutsceneitemdata_t data;
|
||||
|
||||
// Custom user data for the running cutscene.
|
||||
@@ -128,11 +54,12 @@ typedef struct {
|
||||
// See cutsceneSystemSetOnComplete.
|
||||
cutscenecallback_t onComplete;
|
||||
|
||||
// Return stack pushed by CUTSCENE_ITEM_TYPE_INSERT items - see
|
||||
// cutsceneSystemInsertCutscene. Reset (insertStackSize = 0) whenever a
|
||||
// cutscene freshly starts or the system is disposed.
|
||||
cutsceneinsertframe_t insertStack[CUTSCENE_INSERT_STACK_MAX];
|
||||
uint8_t insertStackSize;
|
||||
// The fixed buffer/scene pair cutsceneSystemLoad/cutsceneCutsceneResolve
|
||||
// parse a JSON-authored cutscene into - see CUTSCENE_LOADED_ITEMS_MAX
|
||||
// above. loadedScene.items always points at loadedItems; loadedScene's
|
||||
// itemCount/itemsMax/pause are overwritten on each load.
|
||||
cutsceneitem_t loadedItems[CUTSCENE_LOADED_ITEMS_MAX];
|
||||
cutscene_t loadedScene;
|
||||
} cutscenesystem_t;
|
||||
|
||||
extern cutscenesystem_t CUTSCENE_SYSTEM;
|
||||
@@ -142,12 +69,36 @@ extern cutscenesystem_t CUTSCENE_SYSTEM;
|
||||
*/
|
||||
void cutsceneSystemInit();
|
||||
|
||||
/**
|
||||
* Disposes of the cutscene system, stopping any active cutscene.
|
||||
*/
|
||||
void cutsceneSystemDispose();
|
||||
|
||||
/**
|
||||
* Resets CUTSCENE_SYSTEM to run cutscene from its first item, binding
|
||||
* interact/interacted entities - shared setup used by
|
||||
* cutsceneSystemStartCutsceneWith and
|
||||
* cutsceneSystemStartCutsceneAndGoToMarker. Does not itself advance to the
|
||||
* first item; callers do that afterward (via cutsceneSystemNext or
|
||||
* cutsceneGoTo).
|
||||
*
|
||||
* @param cutscene Pointer to the cutscene to prepare.
|
||||
* @param interact The entity that initiated the interaction (player), or
|
||||
* NULL.
|
||||
* @param interacted The entity being interacted with (NPC), or NULL.
|
||||
*/
|
||||
void cutsceneSystemPrepare(
|
||||
cutscene_t *cutscene,
|
||||
entity_t *interact,
|
||||
entity_t *interacted
|
||||
);
|
||||
|
||||
/**
|
||||
* Start a cutscene with no bound entities.
|
||||
*
|
||||
* @param cutscene Pointer to the cutscene to start.
|
||||
*/
|
||||
void cutsceneSystemStartCutscene(const cutscene_t *cutscene);
|
||||
void cutsceneSystemStartCutscene(cutscene_t *cutscene);
|
||||
|
||||
/**
|
||||
* Start a cutscene with the two entities that triggered it.
|
||||
@@ -157,7 +108,7 @@ void cutsceneSystemStartCutscene(const cutscene_t *cutscene);
|
||||
* @param interacted The entity that was interacted with (NPC).
|
||||
*/
|
||||
void cutsceneSystemStartCutsceneWith(
|
||||
const cutscene_t *cutscene,
|
||||
cutscene_t *cutscene,
|
||||
entity_t *interact,
|
||||
entity_t *interacted
|
||||
);
|
||||
@@ -173,29 +124,29 @@ void cutsceneSystemStartCutsceneWith(
|
||||
* @param marker Marker name to jump to, matched with stringEquals.
|
||||
*/
|
||||
void cutsceneSystemStartCutsceneAndGoToMarker(
|
||||
const cutscene_t *cutscene,
|
||||
cutscene_t *cutscene,
|
||||
const char_t *marker
|
||||
);
|
||||
|
||||
/**
|
||||
* Splices cutscene's items into the running cutscene in place, resuming
|
||||
* the current cutscene right after the CUTSCENE_ITEM_TYPE_INSERT item that
|
||||
* called this once cutscene's items run out - unlike
|
||||
* cutsceneSystemStartCutscene (a one-way jump that replaces the running
|
||||
* cutscene outright). Pushes a return frame onto
|
||||
* CUTSCENE_SYSTEM.insertStack; cutsceneSystemNext pops it once cutscene
|
||||
* ends. Deliberately does not touch pause flags, interact entities,
|
||||
* "last created" state, the text cache or userData - those all keep
|
||||
* whatever the outer cutscene set, since this is meant to feel like
|
||||
* pasting cutscene's items in place rather than starting an independent
|
||||
* cutscene. Because of this, cutscene's own .pause is ignored.
|
||||
* Asserts if no cutscene is currently running, or if inserts are already
|
||||
* nested CUTSCENE_INSERT_STACK_MAX deep.
|
||||
*
|
||||
* Note: cutsceneRestart() resets the insert stack (via
|
||||
* cutsceneSystemPrepare), so a RESTART item used from inside an inserted
|
||||
* cutscene restarts just that inserted cutscene and does not return to
|
||||
* whatever it was inserted into.
|
||||
* Splices cutscene's items into the running cutscene (CUTSCENE_SYSTEM.scene)
|
||||
* in place, right after the currently-executing item, via cutsceneAppendNext
|
||||
* - unlike cutsceneSystemStartCutscene (a one-way jump that replaces the
|
||||
* running cutscene outright), the inserted items simply become part of the
|
||||
* running scene's own item array, so there's nothing separate to "return"
|
||||
* to once they finish - whatever already followed the CUTSCENE_ITEM_TYPE_
|
||||
* INSERT item continues normally, now shifted further down the same array.
|
||||
* Then immediately advances into the first inserted item (see
|
||||
* cutsceneSystemNext), so the splice takes effect within this same call.
|
||||
* Deliberately does not touch pause flags, interact entities, "last
|
||||
* created" state, the text cache or userData - those all keep whatever the
|
||||
* outer cutscene set, since this is meant to feel like pasting cutscene's
|
||||
* items in place rather than starting an independent cutscene. Because of
|
||||
* this, cutscene's own .pause is ignored. Asserts if no cutscene is
|
||||
* currently running, or if the running scene's own itemsMax has no room
|
||||
* left for cutscene's items (see cutscene_t.itemsMax's doc comment - a
|
||||
* cutscene that expects to have items inserted into it at runtime needs to
|
||||
* be declared with spare capacity up front).
|
||||
*
|
||||
* @param cutscene The cutscene whose items to splice in.
|
||||
*/
|
||||
@@ -205,8 +156,8 @@ void cutsceneSystemInsertCutscene(const cutscene_t *cutscene);
|
||||
* Loads and immediately starts a cutscene asset by file name, e.g.
|
||||
* cutsceneSystemLoad("main_menu.jsonc") loads and starts
|
||||
* assets/cutscenes/main_menu.jsonc, parsing it into
|
||||
* CUTSCENE_LOADED_SCENE/ITEMS - the underlying JSON asset entry is
|
||||
* unlocked and reaped immediately once that's done (see
|
||||
* CUTSCENE_SYSTEM.loadedScene/loadedItems - the underlying JSON asset
|
||||
* entry is unlocked and reaped immediately once that's done (see
|
||||
* CUTSCENE_LOADED_ITEMS_MAX's doc comment for why), so it's always
|
||||
* re-read+re-parsed fresh, never assumed still resident from a previous
|
||||
* call. Opens the fatal error overlay (see uiFatalErrorOpen) instead of
|
||||
@@ -217,6 +168,18 @@ void cutsceneSystemInsertCutscene(const cutscene_t *cutscene);
|
||||
*/
|
||||
void cutsceneSystemLoad(const char_t *file);
|
||||
|
||||
/**
|
||||
* Frees whatever yyjson_doc was parsed into CUTSCENE_SYSTEM.loadedItems
|
||||
* last and takes ownership of doc instead. Called by
|
||||
* cutsceneSystemLoad/cutsceneCutsceneResolve once a fresh parse into
|
||||
* loadedItems completes successfully, and from cutsceneSystemDispose to
|
||||
* release the last one at shutdown.
|
||||
*
|
||||
* @param doc The new doc to take ownership of (NULL just frees/clears the
|
||||
* current one).
|
||||
*/
|
||||
void cutsceneLoadedSetDoc(yyjson_doc *doc);
|
||||
|
||||
/**
|
||||
* Restarts the currently running cutscene from its first item,
|
||||
* preserving whatever interact/interacted entities triggered it.
|
||||
@@ -322,8 +285,3 @@ void cutsceneSystemUpdate();
|
||||
* @return Pointer to the current cutscene item.
|
||||
*/
|
||||
const cutsceneitem_t * cutsceneSystemGetCurrentItem();
|
||||
|
||||
/**
|
||||
* Disposes of the cutscene system, stopping any active cutscene.
|
||||
*/
|
||||
void cutsceneSystemDispose();
|
||||
@@ -6,6 +6,7 @@
|
||||
*/
|
||||
|
||||
#include "rpg/cutscene/item/cutsceneitem.h"
|
||||
#include "rpg/cutscene/item/json/cutscenejsonfield.h"
|
||||
|
||||
void cutsceneMarkerStart(
|
||||
const cutsceneitem_t *item,
|
||||
@@ -24,11 +25,11 @@ errorret_t cutsceneMarkerLoad(
|
||||
yyjson_val *itemObj,
|
||||
cutsceneitem_t *item
|
||||
) {
|
||||
const char_t *name = yyjson_get_str(yyjson_obj_get(itemObj, "name"));
|
||||
if(name == NULL) {
|
||||
errorThrow("Cutscene item (MARKER): missing 'name'");
|
||||
if(!cutsceneJsonCopyString(
|
||||
itemObj, "name", NULL, item->marker.name, CUTSCENE_MARKER_NAME_MAX
|
||||
)) {
|
||||
errorThrow("Cutscene item (MARKER): missing/too-long 'name'");
|
||||
}
|
||||
item->marker.name = name;
|
||||
|
||||
errorOk();
|
||||
}
|
||||
|
||||
@@ -8,8 +8,13 @@
|
||||
#pragma once
|
||||
#include "rpg/cutscene/item/cutsceneitembase.h"
|
||||
|
||||
// Also the max length of a marker name referenced (not declared) by other
|
||||
// item types - see cutscenesavedevicecheck_t/cutscenesaveloadallslots_t/
|
||||
// cutscenemodaloptionsmarkers_t.
|
||||
#define CUTSCENE_MARKER_NAME_MAX 32
|
||||
|
||||
typedef struct {
|
||||
const char_t *name;
|
||||
char_t name[CUTSCENE_MARKER_NAME_MAX];
|
||||
} cutscenemarker_t;
|
||||
|
||||
/**
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#pragma once
|
||||
#include "error/error.h"
|
||||
#include "yyjson.h"
|
||||
#include "asset/assetfile.h"
|
||||
#include "cutsceneitembase.h"
|
||||
#include "cutscenecallback.h"
|
||||
#include "cutsceneprint.h"
|
||||
@@ -57,28 +58,37 @@
|
||||
|
||||
typedef struct cutscene_s cutscene_t;
|
||||
|
||||
// A CUTSCENE (or INSERT) item's target. C-authored cutscenes (the
|
||||
// CUTSCENE_CUTSCENE/CUTSCENE_INSERT macros in cutscene.h) resolve cutscene
|
||||
// at compile time and leave name NULL - safe to hold onto indefinitely,
|
||||
// since C-authored cutscenes are compiled directly into their own static
|
||||
// const arrays, never a shared load buffer. JSON-authored cutscenes
|
||||
// (cutsceneCutsceneLoad/cutsceneInsertLoad) do the opposite - only name is
|
||||
// set at parse time, since resolving/locking the referenced asset there
|
||||
// would eagerly load every cutscene a cutscene merely references, even
|
||||
// ones never played. cutsceneCutsceneStart/cutsceneInsertStart resolve
|
||||
// name to cutscene the first time this item actually runs (via
|
||||
// cutsceneCutsceneResolve/cutsceneInsertResolve respectively) but do NOT
|
||||
// cache the result back into this field - with only one shared, fixed
|
||||
// buffer per destination (see cutscenesystem.h), every JSON-authored
|
||||
// Max length of a CUTSCENE/INSERT item's "name" field - bounded by what
|
||||
// fits in the "cutscenes/<name>.jsonc" path built from it (see
|
||||
// cutsceneCutsceneRefLoad), so this is never reached in practice.
|
||||
#define CUTSCENE_CUTSCENE_REF_NAME_MAX ASSET_FILE_NAME_MAX
|
||||
|
||||
// A CUTSCENE (or INSERT) item's target. A C-authored cutscene_t* set
|
||||
// directly (e.g. by a test) leaves name empty ("") - safe to hold onto
|
||||
// indefinitely, since C-authored cutscenes are compiled directly into
|
||||
// their own static arrays, never a shared load buffer. JSON-authored
|
||||
// cutscenes (cutsceneCutsceneLoad/cutsceneInsertLoad) do the opposite -
|
||||
// only name is set at parse time (copied by value out of the JSON doc,
|
||||
// so it stays valid regardless of the doc's own lifetime), since
|
||||
// resolving/locking the referenced asset there would eagerly load every
|
||||
// cutscene a cutscene merely references, even ones never played.
|
||||
// cutsceneCutsceneStart resolves name to cutscene the first time this
|
||||
// item actually runs (via cutsceneCutsceneResolve) but does NOT cache the
|
||||
// result back into this field - with only one shared, fixed buffer (see
|
||||
// CUTSCENE_LOADED_ITEMS_MAX in cutscenesystem.h), every JSON-authored
|
||||
// reference resolves to the same fixed address every time anyway, and
|
||||
// item itself may live inside that very buffer, so caching a pointer into
|
||||
// it here would go stale (or, worse, get clobbered mid-resolve) the
|
||||
// moment a later load overwrites it. Shared by both CUTSCENE
|
||||
// (item->cutsceneRef) and INSERT (item->insert), since both need exactly
|
||||
// this "resolved pointer, or a name to lazily resolve" shape.
|
||||
// item itself may live inside that very buffer, so caching a pointer
|
||||
// into it here would go stale (or, worse, get clobbered mid-resolve) the
|
||||
// moment a later load overwrites it. cutsceneInsertStart instead resolves
|
||||
// AND splices name in one step (cutsceneInsertResolveAndSplice) using a
|
||||
// local scratch buffer that's gone by the time this item's Start
|
||||
// returns, so there's nothing to cache back for INSERT even in principle.
|
||||
// Shared by both CUTSCENE (item->cutsceneRef) and INSERT (item->insert),
|
||||
// since both need exactly this "resolved pointer, or a name to lazily
|
||||
// resolve" shape.
|
||||
typedef struct {
|
||||
const cutscene_t *cutscene;
|
||||
const char_t *name;
|
||||
cutscene_t *cutscene;
|
||||
char_t name[CUTSCENE_CUTSCENE_REF_NAME_MAX];
|
||||
} cutscenecutsceneref_t;
|
||||
|
||||
struct cutsceneitem_s {
|
||||
@@ -215,20 +225,21 @@ void cutsceneCutsceneStart(
|
||||
);
|
||||
|
||||
/**
|
||||
* Resolves a JSON-authored CUTSCENE item's referenced cutscene by name,
|
||||
* locking (forever - never unlocked, matching cutsceneCutsceneLoad's old
|
||||
* convention) and blocking on assets/cutscenes/<name>.jsonc. Called
|
||||
* lazily from cutsceneCutsceneStart the first time the item actually
|
||||
* runs, not at parse time, so a cutscene referencing many others doesn't
|
||||
* load every one of them up front. On failure, opens the fatal error
|
||||
* overlay (matching cutsceneSystemLoad's runtime-failure handling) and
|
||||
* returns NULL.
|
||||
* Resolves a JSON-authored CUTSCENE item's referenced cutscene by name:
|
||||
* locks+loads assets/cutscenes/<name>.jsonc, parses it into
|
||||
* CUTSCENE_SYSTEM.loadedItems/loadedScene, then unlocks and reaps the asset entry
|
||||
* immediately (see CUTSCENE_LOADED_ITEMS_MAX's doc comment for why).
|
||||
* Called lazily from cutsceneCutsceneStart the first time the item
|
||||
* actually runs, not at parse time, so a cutscene referencing many others
|
||||
* doesn't load every one of them up front. On failure, opens the fatal
|
||||
* error overlay (matching cutsceneSystemLoad's runtime-failure handling)
|
||||
* and returns NULL.
|
||||
*
|
||||
* @param name Bare cutscene name (without "cutscenes/" prefix or
|
||||
* ".jsonc" suffix).
|
||||
* @return The resolved cutscene, or NULL on failure.
|
||||
*/
|
||||
const cutscene_t * cutsceneCutsceneResolve(const char_t *name);
|
||||
cutscene_t * cutsceneCutsceneResolve(const char_t *name);
|
||||
|
||||
/**
|
||||
* Updates a nested-cutscene item. By the time this would run, control
|
||||
@@ -283,17 +294,13 @@ errorret_t cutsceneCutsceneRefLoad(
|
||||
|
||||
/**
|
||||
* Starts an INSERT item, splicing the referenced cutscene's items into the
|
||||
* running cutscene in place - unlike CUTSCENE (a one-way jump that replaces
|
||||
* the running cutscene outright), control returns to the item right after
|
||||
* this one once the inserted items run out. If item->insert.cutscene hasn't
|
||||
* been resolved yet (a JSON-authored reference, only known by name so
|
||||
* far), resolves it first via cutsceneInsertResolve - not
|
||||
* cutsceneCutsceneResolve, so the referenced cutscene lands in the
|
||||
* dedicated CUTSCENE_INSERT_ITEMS buffer rather than overwriting
|
||||
* CUTSCENE_LOADED_ITEMS out from under whatever outer cutscene is
|
||||
* currently running (see cutscenesystem.h's CUTSCENE_INSERT_ITEMS_MAX doc
|
||||
* comment). Not cached back into item, same reasoning as
|
||||
* cutsceneCutsceneStart.
|
||||
* running cutscene in place via cutsceneSystemInsertCutscene - unlike
|
||||
* CUTSCENE (a one-way jump that replaces the running cutscene outright),
|
||||
* whatever already followed this item continues normally once the
|
||||
* inserted items run out, just shifted further down the same array. If
|
||||
* item->insert.cutscene is set (a compile-time target), splices it
|
||||
* directly; otherwise (a JSON-authored reference, only known by name so
|
||||
* far) resolves and splices it via cutsceneInsertResolveAndSplice.
|
||||
*
|
||||
* @param item The cutscene item.
|
||||
* @param data Runtime data storage.
|
||||
@@ -304,20 +311,23 @@ void cutsceneInsertStart(
|
||||
);
|
||||
|
||||
/**
|
||||
* Resolves a JSON-authored INSERT item's referenced cutscene by name -
|
||||
* locks+loads assets/cutscenes/<name>.jsonc (as a plain
|
||||
* ASSET_LOADER_TYPE_JSON asset, same path/key convention as
|
||||
* cutsceneCutsceneResolve - which destination buffer it lands in is
|
||||
* decided purely by which resolve function is called, not stored on the
|
||||
* asset entry itself), then parses it into CUTSCENE_INSERT_ITEMS rather
|
||||
* than CUTSCENE_LOADED_ITEMS. On failure, opens the fatal error overlay
|
||||
* and returns NULL.
|
||||
* Resolves a JSON-authored INSERT item's referenced cutscene by name and
|
||||
* splices it into the running cutscene (CUTSCENE_SYSTEM.scene): locks+
|
||||
* loads assets/cutscenes/<name>.jsonc as a plain ASSET_LOADER_TYPE_JSON
|
||||
* asset, parses it into a small scratch buffer local to this call
|
||||
* (capacity CUTSCENE_INSERT_ITEMS_MAX), then immediately frees the parsed
|
||||
* doc - unlike cutsceneCutsceneResolve's CUTSCENE_SYSTEM.loadedScene, nothing
|
||||
* needs to persist past this call: cutsceneSystemInsertCutscene copies the
|
||||
* scratch items into the running scene's own array, and every cutscene
|
||||
* item field that could reference the doc's memory owns its string data
|
||||
* by value (see cutscenecutsceneref_t/cutscenemarker_t's doc comments), so
|
||||
* the doc is disposable the moment parsing finishes. On failure, opens the
|
||||
* fatal error overlay, matching cutsceneCutsceneResolve.
|
||||
*
|
||||
* @param name Bare cutscene name (without "cutscenes/" prefix or
|
||||
* ".jsonc" suffix).
|
||||
* @return The resolved cutscene, or NULL on failure.
|
||||
*/
|
||||
const cutscene_t * cutsceneInsertResolve(const char_t *name);
|
||||
void cutsceneInsertResolveAndSplice(const char_t *name);
|
||||
|
||||
/**
|
||||
* Updates an INSERT item. By the time this would run, control has already
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
|
||||
#include "rpg/cutscene/item/cutsceneitem.h"
|
||||
#include "rpg/cutscene/cutscenesystem.h"
|
||||
#include "rpg/cutscene/item/json/cutscenejsonfield.h"
|
||||
#include "save/save.h"
|
||||
|
||||
void cutsceneSaveDeviceCheckCallback(savedevice_t *device, void *user) {
|
||||
@@ -36,18 +37,21 @@ errorret_t cutsceneSaveDeviceCheckLoad(
|
||||
yyjson_val *itemObj,
|
||||
cutsceneitem_t *item
|
||||
) {
|
||||
const char_t *successMarker =
|
||||
yyjson_get_str(yyjson_obj_get(itemObj, "successMarker"));
|
||||
const char_t *failureMarker =
|
||||
yyjson_get_str(yyjson_obj_get(itemObj, "failureMarker"));
|
||||
if(successMarker == NULL || failureMarker == NULL) {
|
||||
if(
|
||||
!cutsceneJsonCopyString(
|
||||
itemObj, "successMarker", NULL, item->saveDeviceCheck.successMarker,
|
||||
CUTSCENE_MARKER_NAME_MAX
|
||||
) ||
|
||||
!cutsceneJsonCopyString(
|
||||
itemObj, "failureMarker", NULL, item->saveDeviceCheck.failureMarker,
|
||||
CUTSCENE_MARKER_NAME_MAX
|
||||
)
|
||||
) {
|
||||
errorThrow(
|
||||
"Cutscene item (SAVE_DEVICE_CHECK): needs 'successMarker'/"
|
||||
"'failureMarker'"
|
||||
);
|
||||
}
|
||||
item->saveDeviceCheck.successMarker = successMarker;
|
||||
item->saveDeviceCheck.failureMarker = failureMarker;
|
||||
|
||||
errorOk();
|
||||
}
|
||||
|
||||
@@ -7,10 +7,11 @@
|
||||
|
||||
#pragma once
|
||||
#include "rpg/cutscene/item/cutsceneitembase.h"
|
||||
#include "rpg/cutscene/item/control/cutscenemarker.h"
|
||||
|
||||
typedef struct {
|
||||
const char_t *successMarker;
|
||||
const char_t *failureMarker;
|
||||
char_t successMarker[CUTSCENE_MARKER_NAME_MAX];
|
||||
char_t failureMarker[CUTSCENE_MARKER_NAME_MAX];
|
||||
} cutscenesavedevicecheck_t;
|
||||
|
||||
/**
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
|
||||
#include "rpg/cutscene/item/cutsceneitem.h"
|
||||
#include "rpg/cutscene/cutscenesystem.h"
|
||||
#include "rpg/cutscene/item/json/cutscenejsonfield.h"
|
||||
#include "save/save.h"
|
||||
|
||||
void cutsceneSaveLoadAllSlotsStart(
|
||||
@@ -34,18 +35,21 @@ errorret_t cutsceneSaveLoadAllSlotsLoad(
|
||||
yyjson_val *itemObj,
|
||||
cutsceneitem_t *item
|
||||
) {
|
||||
const char_t *successMarker =
|
||||
yyjson_get_str(yyjson_obj_get(itemObj, "successMarker"));
|
||||
const char_t *failureMarker =
|
||||
yyjson_get_str(yyjson_obj_get(itemObj, "failureMarker"));
|
||||
if(successMarker == NULL || failureMarker == NULL) {
|
||||
if(
|
||||
!cutsceneJsonCopyString(
|
||||
itemObj, "successMarker", NULL, item->saveLoadAllSlots.successMarker,
|
||||
CUTSCENE_MARKER_NAME_MAX
|
||||
) ||
|
||||
!cutsceneJsonCopyString(
|
||||
itemObj, "failureMarker", NULL, item->saveLoadAllSlots.failureMarker,
|
||||
CUTSCENE_MARKER_NAME_MAX
|
||||
)
|
||||
) {
|
||||
errorThrow(
|
||||
"Cutscene item (SAVE_LOAD_ALL_SLOTS): needs 'successMarker'/"
|
||||
"'failureMarker'"
|
||||
);
|
||||
}
|
||||
item->saveLoadAllSlots.successMarker = successMarker;
|
||||
item->saveLoadAllSlots.failureMarker = failureMarker;
|
||||
|
||||
errorOk();
|
||||
}
|
||||
|
||||
@@ -7,10 +7,11 @@
|
||||
|
||||
#pragma once
|
||||
#include "rpg/cutscene/item/cutsceneitembase.h"
|
||||
#include "rpg/cutscene/item/control/cutscenemarker.h"
|
||||
|
||||
typedef struct {
|
||||
const char_t *successMarker;
|
||||
const char_t *failureMarker;
|
||||
char_t successMarker[CUTSCENE_MARKER_NAME_MAX];
|
||||
char_t failureMarker[CUTSCENE_MARKER_NAME_MAX];
|
||||
} cutscenesaveloadallslots_t;
|
||||
|
||||
/**
|
||||
|
||||
@@ -32,10 +32,18 @@ void cutsceneModalOptionsMarkersStart(
|
||||
) {
|
||||
const cutscenemodaloptionsmarkers_t *options = &item->modalOptionsMarkers;
|
||||
|
||||
// uiModalOpen wants an array of string pointers; options->options is a
|
||||
// 2D array of owned char buffers (see the struct's doc comment), so
|
||||
// build the pointer array it expects here.
|
||||
const char_t *optionPtrs[CUTSCENE_MODAL_OPTIONS_MARKERS_MAX];
|
||||
for(uint8_t i = 0; i < options->optionCount; i++) {
|
||||
optionPtrs[i] = options->options[i];
|
||||
}
|
||||
|
||||
uiModalOpen(
|
||||
options->title,
|
||||
options->message,
|
||||
options->options,
|
||||
optionPtrs,
|
||||
options->optionCount,
|
||||
cutsceneModalOptionsMarkersCallback,
|
||||
NULL,
|
||||
@@ -82,17 +90,21 @@ errorret_t cutsceneModalOptionsMarkersLoad(
|
||||
yyjson_val *optionVal;
|
||||
size_t o = 0;
|
||||
while((optionVal = yyjson_arr_iter_next(&iter)) != NULL) {
|
||||
const char_t *text = yyjson_get_str(yyjson_obj_get(optionVal, "text"));
|
||||
const char_t *marker =
|
||||
yyjson_get_str(yyjson_obj_get(optionVal, "marker"));
|
||||
if(text == NULL || marker == NULL) {
|
||||
if(
|
||||
!cutsceneJsonCopyString(
|
||||
optionVal, "text", NULL, item->modalOptionsMarkers.options[o],
|
||||
UI_MODAL_OPTION_TEXT_MAX
|
||||
) ||
|
||||
!cutsceneJsonCopyString(
|
||||
optionVal, "marker", NULL, item->modalOptionsMarkers.markers[o],
|
||||
CUTSCENE_MARKER_NAME_MAX
|
||||
)
|
||||
) {
|
||||
errorThrow(
|
||||
"Cutscene item (MODAL_OPTIONS_MARKERS): options[%u] needs 'text' "
|
||||
"and 'marker'", (uint32_t)o
|
||||
"Cutscene item (MODAL_OPTIONS_MARKERS): options[%u] needs "
|
||||
"'text'/'marker'", (uint32_t)o
|
||||
);
|
||||
}
|
||||
item->modalOptionsMarkers.options[o] = text;
|
||||
item->modalOptionsMarkers.markers[o] = marker;
|
||||
o++;
|
||||
}
|
||||
|
||||
|
||||
@@ -8,9 +8,11 @@
|
||||
#pragma once
|
||||
#include "rpg/cutscene/item/cutsceneitembase.h"
|
||||
#include "rpg/cutscene/item/ui/cutscenemodal.h"
|
||||
#include "rpg/cutscene/item/control/cutscenemarker.h"
|
||||
#include "ui/widget/uimodal.h"
|
||||
|
||||
// Backs CUTSCENE_MODAL_OPTIONS_ONE and CUTSCENE_MODAL_OPTIONS_TWO, so two
|
||||
// is the most either macro ever needs.
|
||||
// Two options is the most either JSON MODAL_OPTIONS_MARKERS item ever
|
||||
// needs.
|
||||
#define CUTSCENE_MODAL_OPTIONS_MARKERS_MAX 2
|
||||
|
||||
typedef struct {
|
||||
@@ -20,9 +22,11 @@ typedef struct {
|
||||
char_t message[CUTSCENE_MODAL_MESSAGE_MAX_CHARS];
|
||||
|
||||
// Each option's display text/locale message ID, and the marker to
|
||||
// cutsceneGoTo when that option is selected.
|
||||
const char_t *options[CUTSCENE_MODAL_OPTIONS_MARKERS_MAX];
|
||||
const char_t *markers[CUTSCENE_MODAL_OPTIONS_MARKERS_MAX];
|
||||
// cutsceneGoTo when that option is selected. Copied by value at parse
|
||||
// time (see cutsceneModalOptionsMarkersLoad), so they stay valid
|
||||
// regardless of the backing JSON doc's lifetime.
|
||||
char_t options[CUTSCENE_MODAL_OPTIONS_MARKERS_MAX][UI_MODAL_OPTION_TEXT_MAX];
|
||||
char_t markers[CUTSCENE_MODAL_OPTIONS_MARKERS_MAX][CUTSCENE_MARKER_NAME_MAX];
|
||||
uint8_t optionCount;
|
||||
} cutscenemodaloptionsmarkers_t;
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ void entityInteractWith(entity_t *player, entity_t *target) {
|
||||
break;
|
||||
|
||||
case ENTITY_INTERACT_CUTSCENE_NAME: {
|
||||
const cutscene_t *cutscene =
|
||||
cutscene_t *cutscene =
|
||||
cutsceneCutsceneResolve(target->interact.data.cutsceneName);
|
||||
if(cutscene != NULL) {
|
||||
cutsceneSystemStartCutsceneWith(cutscene, player, target);
|
||||
|
||||
@@ -31,9 +31,10 @@ typedef enum {
|
||||
typedef union {
|
||||
// A compile-time cutscene_t* (a C-authored, test-only cutscene) - safe
|
||||
// to hold onto indefinitely, since it's compiled directly into its own
|
||||
// static const arrays, never the shared CUTSCENE_LOADED_ITEMS buffer
|
||||
// (see cutscenesystem.h).
|
||||
const cutscene_t *cutscene;
|
||||
// static arrays, never the shared CUTSCENE_SYSTEM.loadedItems buffer (see
|
||||
// cutscenesystem.h). Non-const since cutsceneSystemStartCutsceneWith may
|
||||
// splice CUTSCENE_ITEM_TYPE_INSERT items into it at runtime.
|
||||
cutscene_t *cutscene;
|
||||
// A bare cutscene name (as passed to cutsceneCutsceneResolve), resolved
|
||||
// lazily at interact time rather than once up front - only one
|
||||
// JSON-authored cutscene is ever resident at a time (see
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
};
|
||||
|
||||
|
||||
@@ -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),
|
||||
};
|
||||
|
||||
|
||||
@@ -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
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user