Add CUTSCENE_INSERT item to splice another cutscene's items in place
Unlike CUTSCENE (a one-way jump that replaces the running cutscene), INSERT splices a referenced cutscene's items into the running one and resumes right after itself once they run out. Backed by a small return stack on CUTSCENE_SYSTEM so inserts can nest. Co-Authored-By: Claude Sonnet 5 <[email protected]>
This commit is contained in:
@@ -62,3 +62,20 @@ typedef struct cutscene_s {
|
||||
.type = CUTSCENE_ITEM_TYPE_CUTSCENE, \
|
||||
.cutsceneRef = { .cutscene = CUTSCENE_REFERENCE(CUTSCENE), .name = NULL } \
|
||||
}
|
||||
|
||||
/**
|
||||
* Splices another CUTSCENE(NAME, ...)-declared cutscene's items into this
|
||||
* one in place, resuming right after this item once the referenced
|
||||
* cutscene's items run out - unlike CUTSCENE_CUTSCENE (a one-way jump that
|
||||
* never returns). Has no dedicated item header of its own, same rationale
|
||||
* as CUTSCENE_CUTSCENE - cutsceneInsertStart/Update/Load are defined
|
||||
* inline in cutsceneitem.h/.c, tightly bound to the asset/loader system.
|
||||
*
|
||||
* @param CUTSCENE The referenced cutscene's NAME, as passed to
|
||||
* CUTSCENE(...).
|
||||
*/
|
||||
#define CUTSCENE_INSERT(CUTSCENE) \
|
||||
{ \
|
||||
.type = CUTSCENE_ITEM_TYPE_INSERT, \
|
||||
.insert = { .cutscene = CUTSCENE_REFERENCE(CUTSCENE), .name = NULL } \
|
||||
}
|
||||
|
||||
@@ -40,6 +40,7 @@ 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) {
|
||||
@@ -107,6 +108,21 @@ void cutsceneSystemNext() {
|
||||
|
||||
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
|
||||
@@ -129,6 +145,7 @@ void cutsceneSystemNext() {
|
||||
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;
|
||||
@@ -140,6 +157,27 @@ void cutsceneSystemNext() {
|
||||
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,
|
||||
@@ -259,4 +297,5 @@ void cutsceneSystemDispose() {
|
||||
CUTSCENE_SYSTEM.textMiniLastCreated = CUTSCENE_TEXT_MINI_LAST_CREATED;
|
||||
CUTSCENE_SYSTEM.textCache[0] = '\0';
|
||||
CUTSCENE_SYSTEM.onComplete = NULL;
|
||||
CUTSCENE_SYSTEM.insertStackSize = 0;
|
||||
}
|
||||
|
||||
@@ -26,6 +26,18 @@ typedef struct entity_s entity_t;
|
||||
// CUTSCENE_ITEM_TYPE_KEYBOARD item is never truncated caching it here.
|
||||
#define CUTSCENE_TEXT_CACHE_MAX 64
|
||||
|
||||
// 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;
|
||||
uint8_t currentItem;
|
||||
@@ -52,6 +64,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;
|
||||
} cutscenesystem_t;
|
||||
|
||||
extern cutscenesystem_t CUTSCENE_SYSTEM;
|
||||
@@ -96,6 +114,30 @@ void cutsceneSystemStartCutsceneAndGoToMarker(
|
||||
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/.dataSize are 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.
|
||||
*
|
||||
* @param cutscene The cutscene whose items to splice in.
|
||||
*/
|
||||
void cutsceneSystemInsertCutscene(const cutscene_t *cutscene);
|
||||
|
||||
/**
|
||||
* Locks, loads and immediately starts a cutscene asset by file name,
|
||||
* e.g. cutsceneSystemLoad("main_menu.jsonc") loads and starts
|
||||
|
||||
@@ -333,6 +333,13 @@ cutsceneitemcallbacks_t CUTSCENE_ITEM_CALLBACKS[CUTSCENE_ITEM_TYPE_COUNT] = {
|
||||
.update = cutsceneRegularBattleUpdate,
|
||||
.jsonName = "REGULAR_BATTLE",
|
||||
.load = cutsceneItemLoadNoop
|
||||
},
|
||||
|
||||
[CUTSCENE_ITEM_TYPE_INSERT] = {
|
||||
.init = cutsceneInsertStart,
|
||||
.update = cutsceneInsertUpdate,
|
||||
.jsonName = "INSERT",
|
||||
.load = cutsceneInsertLoad
|
||||
}
|
||||
};
|
||||
|
||||
@@ -408,10 +415,18 @@ errorret_t cutsceneCutsceneLoad(
|
||||
cutsceneitem_t *item,
|
||||
uint8_t *pool,
|
||||
size_t *poolOffset
|
||||
) {
|
||||
return cutsceneCutsceneRefLoad(itemObj, &item->cutsceneRef, "CUTSCENE");
|
||||
}
|
||||
|
||||
errorret_t cutsceneCutsceneRefLoad(
|
||||
yyjson_val *itemObj,
|
||||
cutscenecutsceneref_t *outRef,
|
||||
const char_t *itemTypeLabel
|
||||
) {
|
||||
const char_t *name = yyjson_get_str(yyjson_obj_get(itemObj, "name"));
|
||||
if(name == NULL) {
|
||||
errorThrow("Cutscene item (CUTSCENE): missing 'name'");
|
||||
errorThrow("Cutscene item (%s): missing 'name'", itemTypeLabel);
|
||||
}
|
||||
|
||||
// Only validate that the eventual "cutscenes/<name>.jsonc" path would
|
||||
@@ -419,11 +434,41 @@ errorret_t cutsceneCutsceneLoad(
|
||||
// actually runs (see cutsceneCutsceneStart/cutsceneCutsceneResolve).
|
||||
int32_t pathLen = stringFormat(NULL, 0, "cutscenes/%s.jsonc", name);
|
||||
if(pathLen < 0 || (size_t)pathLen >= ASSET_FILE_NAME_MAX) {
|
||||
errorThrow("Cutscene item (CUTSCENE): 'name' too long");
|
||||
errorThrow("Cutscene item (%s): 'name' too long", itemTypeLabel);
|
||||
}
|
||||
|
||||
item->cutsceneRef.cutscene = NULL;
|
||||
item->cutsceneRef.name = name;
|
||||
outRef->cutscene = NULL;
|
||||
outRef->name = name;
|
||||
|
||||
errorOk();
|
||||
}
|
||||
|
||||
void cutsceneInsertStart(
|
||||
const cutsceneitem_t *item,
|
||||
cutsceneitemdata_t *data
|
||||
) {
|
||||
const cutscene_t *cutscene = item->insert.cutscene;
|
||||
if(cutscene == NULL && item->insert.name != NULL) {
|
||||
cutscene = cutsceneCutsceneResolve(item->insert.name);
|
||||
// Cache the resolved pointer back so replaying this same item never
|
||||
// re-resolves/re-locks the asset again, same as cutsceneCutsceneStart.
|
||||
((cutsceneitem_t *)item)->insert.cutscene = cutscene;
|
||||
}
|
||||
if(cutscene != NULL) cutsceneSystemInsertCutscene(cutscene);
|
||||
}
|
||||
|
||||
bool_t cutsceneInsertUpdate(
|
||||
const cutsceneitem_t *item,
|
||||
cutsceneitemdata_t *data
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
errorret_t cutsceneInsertLoad(
|
||||
yyjson_val *itemObj,
|
||||
cutsceneitem_t *item,
|
||||
uint8_t *pool,
|
||||
size_t *poolOffset
|
||||
) {
|
||||
return cutsceneCutsceneRefLoad(itemObj, &item->insert, "INSERT");
|
||||
}
|
||||
|
||||
@@ -57,15 +57,18 @@
|
||||
|
||||
typedef struct cutscene_s cutscene_t;
|
||||
|
||||
// A CUTSCENE item's target. C-authored cutscenes (the CUTSCENE_CUTSCENE
|
||||
// macro in cutscene.h) resolve cutscene at compile time and leave name
|
||||
// NULL. JSON-authored cutscenes (cutsceneCutsceneLoad) 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 resolves
|
||||
// name to cutscene the first time this item actually runs (via
|
||||
// cutsceneCutsceneResolve) and caches the result back into this field so
|
||||
// replaying the same item never re-resolves it.
|
||||
// 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. JSON-authored cutscenes
|
||||
// (cutsceneCutsceneRefLoad) 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) and cache the result back into this field so
|
||||
// replaying the same item never re-resolves 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.
|
||||
typedef struct {
|
||||
const cutscene_t *cutscene;
|
||||
const char_t *name;
|
||||
@@ -81,6 +84,7 @@ struct cutsceneitem_s {
|
||||
cutscenecallback_t callback;
|
||||
cutscenewait_t wait;
|
||||
cutscenecutsceneref_t cutsceneRef;
|
||||
cutscenecutsceneref_t insert;
|
||||
cutsceneentityteleport_t entityTeleport;
|
||||
cutsceneentitywalkto_t entityWalkTo;
|
||||
cutscenefade_t fade;
|
||||
@@ -262,3 +266,67 @@ errorret_t cutsceneCutsceneLoad(
|
||||
uint8_t *pool,
|
||||
size_t *poolOffset
|
||||
);
|
||||
|
||||
/**
|
||||
* Shared JSON parsing body for both CUTSCENE and INSERT items - both need
|
||||
* exactly a 'name' string field, validated to fit the eventual
|
||||
* "cutscenes/<name>.jsonc" asset path. Only stores the name (see
|
||||
* cutsceneCutsceneLoad for why resolving is deferred).
|
||||
*
|
||||
* @param itemObj The item's JSON object.
|
||||
* @param outRef Destination ref, already zeroed by the caller.
|
||||
* @param itemTypeLabel Item type name used only in the error message on
|
||||
* failure (e.g. "CUTSCENE" or "INSERT").
|
||||
* @return Error code indicating success or failure of the parse.
|
||||
*/
|
||||
errorret_t cutsceneCutsceneRefLoad(
|
||||
yyjson_val *itemObj,
|
||||
cutscenecutsceneref_t *outRef,
|
||||
const char_t *itemTypeLabel
|
||||
);
|
||||
|
||||
/**
|
||||
* 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 and caches it first via cutsceneCutsceneResolve, same as
|
||||
* cutsceneCutsceneStart.
|
||||
*
|
||||
* @param item The cutscene item.
|
||||
* @param data Runtime data storage.
|
||||
*/
|
||||
void cutsceneInsertStart(
|
||||
const cutsceneitem_t *item,
|
||||
cutsceneitemdata_t *data
|
||||
);
|
||||
|
||||
/**
|
||||
* Updates an INSERT item. By the time this would run, control has already
|
||||
* moved on to the inserted items, so this always reports incomplete.
|
||||
*
|
||||
* @param item The cutscene item.
|
||||
* @param data Runtime data storage.
|
||||
* @returns false always.
|
||||
*/
|
||||
bool_t cutsceneInsertUpdate(
|
||||
const cutsceneitem_t *item,
|
||||
cutsceneitemdata_t *data
|
||||
);
|
||||
|
||||
/**
|
||||
* Parses an INSERT cutscene item's JSON fields into item.
|
||||
*
|
||||
* @param itemObj The item's JSON object.
|
||||
* @param item Destination item, already zeroed by the caller.
|
||||
* @param pool Unused (INSERT needs no pool storage).
|
||||
* @param poolOffset Unused (INSERT needs no pool storage).
|
||||
* @return Error code indicating success or failure of the parse.
|
||||
*/
|
||||
errorret_t cutsceneInsertLoad(
|
||||
yyjson_val *itemObj,
|
||||
cutsceneitem_t *item,
|
||||
uint8_t *pool,
|
||||
size_t *poolOffset
|
||||
);
|
||||
|
||||
@@ -63,6 +63,7 @@ typedef enum {
|
||||
CUTSCENE_ITEM_TYPE_IDLE,
|
||||
CUTSCENE_ITEM_TYPE_UI_SHOW,
|
||||
CUTSCENE_ITEM_TYPE_REGULAR_BATTLE,
|
||||
CUTSCENE_ITEM_TYPE_INSERT,
|
||||
|
||||
CUTSCENE_ITEM_TYPE_COUNT
|
||||
} cutsceneitemtype_t;
|
||||
|
||||
@@ -177,6 +177,201 @@ static void test_cutsceneSystemDisposeResetsState(void **state) {
|
||||
assert_null(CUTSCENE_SYSTEM.entityInteracted);
|
||||
}
|
||||
|
||||
// --- CUTSCENE_INSERT ---------------------------------------------------
|
||||
|
||||
static uint8_t insertOrderLog[8];
|
||||
static uint8_t insertOrderLogCount;
|
||||
|
||||
static void recordOrder0(void *userData) { insertOrderLog[insertOrderLogCount++] = 0; }
|
||||
static void recordOrder1(void *userData) { insertOrderLog[insertOrderLogCount++] = 1; }
|
||||
static void recordOrder2(void *userData) { insertOrderLog[insertOrderLogCount++] = 2; }
|
||||
static void recordOrderA(void *userData) { insertOrderLog[insertOrderLogCount++] = 3; }
|
||||
static void recordOrderB(void *userData) { insertOrderLog[insertOrderLogCount++] = 4; }
|
||||
|
||||
CUTSCENE(TEST_INSERT_SNIPPET, 0, NONE,
|
||||
CUTSCENE_CALLBACK(recordOrderA),
|
||||
CUTSCENE_WAIT(2.0f),
|
||||
CUTSCENE_CALLBACK(recordOrderB)
|
||||
);
|
||||
|
||||
CUTSCENE(TEST_INSERT_OUTER, 0, NONE,
|
||||
CUTSCENE_CALLBACK(recordOrder0),
|
||||
CUTSCENE_WAIT(1.0f),
|
||||
CUTSCENE_CALLBACK(recordOrder1),
|
||||
CUTSCENE_INSERT(TEST_INSERT_SNIPPET),
|
||||
CUTSCENE_CALLBACK(recordOrder2)
|
||||
);
|
||||
|
||||
static void test_cutsceneInsertSplicesItemsInPlaceThenResumes(void **state) {
|
||||
|
||||
insertOrderLogCount = 0;
|
||||
cutsceneSystemInit();
|
||||
cutsceneSystemStartCutscene(&CUTSCENE_TEST_INSERT_OUTER);
|
||||
|
||||
TIME.delta = 1.5f;// elapses the outer WAIT(1.0)
|
||||
cutsceneSystemUpdate();// order0 done -> starts the outer WAIT
|
||||
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);
|
||||
|
||||
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
|
||||
|
||||
assert_ptr_equal(CUTSCENE_SYSTEM.scene, &CUTSCENE_TEST_INSERT_OUTER);
|
||||
|
||||
cutsceneSystemUpdate();// order2 done -> cutscene ends naturally
|
||||
assert_null(CUTSCENE_SYSTEM.scene);
|
||||
|
||||
const uint8_t expected[] = { 0, 1, 3, 4, 2 };
|
||||
assert_int_equal(insertOrderLogCount, sizeof(expected));
|
||||
assert_memory_equal(insertOrderLog, expected, sizeof(expected));
|
||||
}
|
||||
|
||||
CUTSCENE(TEST_INSERT_AS_LAST_ITEM_SNIPPET, 0, NONE,
|
||||
CUTSCENE_CALLBACK(recordOrderA)
|
||||
);
|
||||
|
||||
CUTSCENE(TEST_INSERT_AS_LAST_ITEM_OUTER, 0, NONE,
|
||||
CUTSCENE_CALLBACK(recordOrder0),
|
||||
CUTSCENE_INSERT(TEST_INSERT_AS_LAST_ITEM_SNIPPET)
|
||||
);
|
||||
|
||||
static void test_cutsceneInsertAsLastItemEndsCutsceneNaturally(
|
||||
void **state
|
||||
) {
|
||||
|
||||
insertOrderLogCount = 0;
|
||||
cutsceneSystemInit();
|
||||
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
|
||||
);
|
||||
|
||||
// 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
|
||||
// of the whole cutscene, not get stuck.
|
||||
cutsceneSystemUpdate();
|
||||
assert_null(CUTSCENE_SYSTEM.scene);
|
||||
|
||||
const uint8_t expected[] = { 0, 3 };
|
||||
assert_int_equal(insertOrderLogCount, sizeof(expected));
|
||||
assert_memory_equal(insertOrderLog, expected, sizeof(expected));
|
||||
}
|
||||
|
||||
CUTSCENE(TEST_INSERT_NESTED_INNER, 0, NONE,
|
||||
CUTSCENE_CALLBACK(recordOrderA)
|
||||
);
|
||||
|
||||
// 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.
|
||||
CUTSCENE(TEST_INSERT_NESTED_MID, 0, NONE,
|
||||
CUTSCENE_INSERT(TEST_INSERT_NESTED_INNER),
|
||||
CUTSCENE_CALLBACK(recordOrderB)
|
||||
);
|
||||
|
||||
CUTSCENE(TEST_INSERT_NESTED_OUTER, 0, NONE,
|
||||
CUTSCENE_CALLBACK(recordOrder0),
|
||||
CUTSCENE_INSERT(TEST_INSERT_NESTED_MID),
|
||||
CUTSCENE_CALLBACK(recordOrder2)
|
||||
);
|
||||
|
||||
static void test_cutsceneInsertNestsThroughMultipleLevels(void **state) {
|
||||
|
||||
insertOrderLogCount = 0;
|
||||
cutsceneSystemInit();
|
||||
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.
|
||||
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);
|
||||
|
||||
cutsceneSystemUpdate();// order2 done -> ends naturally
|
||||
assert_null(CUTSCENE_SYSTEM.scene);
|
||||
|
||||
const uint8_t expected[] = { 0, 3, 4, 2 };
|
||||
assert_int_equal(insertOrderLogCount, sizeof(expected));
|
||||
assert_memory_equal(insertOrderLog, expected, sizeof(expected));
|
||||
}
|
||||
|
||||
CUTSCENE(TEST_INSERT_OVERFLOW_LEVEL5, 0, NONE,
|
||||
CUTSCENE_CALLBACK(recordOrderA)
|
||||
);
|
||||
CUTSCENE(TEST_INSERT_OVERFLOW_LEVEL4, 0, NONE,
|
||||
CUTSCENE_INSERT(TEST_INSERT_OVERFLOW_LEVEL5)
|
||||
);
|
||||
CUTSCENE(TEST_INSERT_OVERFLOW_LEVEL3, 0, NONE,
|
||||
CUTSCENE_INSERT(TEST_INSERT_OVERFLOW_LEVEL4)
|
||||
);
|
||||
CUTSCENE(TEST_INSERT_OVERFLOW_LEVEL2, 0, NONE,
|
||||
CUTSCENE_INSERT(TEST_INSERT_OVERFLOW_LEVEL3)
|
||||
);
|
||||
CUTSCENE(TEST_INSERT_OVERFLOW_LEVEL1, 0, NONE,
|
||||
CUTSCENE_INSERT(TEST_INSERT_OVERFLOW_LEVEL2)
|
||||
);
|
||||
CUTSCENE(TEST_INSERT_OVERFLOW_LEVEL0, 0, NONE,
|
||||
CUTSCENE_INSERT(TEST_INSERT_OVERFLOW_LEVEL1)
|
||||
);
|
||||
|
||||
static void test_cutsceneInsertAssertsWhenNestingExceedsStackMax(
|
||||
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)
|
||||
);
|
||||
}
|
||||
|
||||
static void noopInsertCallback(void *userData) {}
|
||||
|
||||
CUTSCENE(TEST_INSERT_PERSIST_SNIPPET, 0, ALL,
|
||||
CUTSCENE_CALLBACK(noopInsertCallback)
|
||||
);
|
||||
|
||||
CUTSCENE(TEST_INSERT_PERSIST_OUTER, 0, NPC,
|
||||
CUTSCENE_INSERT(TEST_INSERT_PERSIST_SNIPPET)
|
||||
);
|
||||
|
||||
static void test_cutsceneInsertDoesNotResetPauseOrInteractEntities(
|
||||
void **state
|
||||
) {
|
||||
|
||||
cutsceneSystemInit();
|
||||
entityInit(&ENTITIES[3], ENTITY_TYPE_PLAYER);
|
||||
entityInit(&ENTITIES[4], ENTITY_TYPE_NPC);
|
||||
|
||||
cutsceneSystemStartCutsceneWith(
|
||||
&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
|
||||
);
|
||||
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
|
||||
// cutscene's NPC pause, already in effect, stands.
|
||||
assert_int_equal(CUTSCENE_SYSTEM.pause, CUTSCENE_PAUSE_NPC);
|
||||
}
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
const struct CMUnitTest tests[] = {
|
||||
cmocka_unit_test(test_cutsceneSystemStartSetsUpInitialItem),
|
||||
@@ -188,6 +383,11 @@ int main(int argc, char** argv) {
|
||||
cmocka_unit_test(test_cutsceneSystemGetAreaId),
|
||||
cmocka_unit_test(test_cutsceneSystemGetTextMiniId),
|
||||
cmocka_unit_test(test_cutsceneSystemDisposeResetsState),
|
||||
cmocka_unit_test(test_cutsceneInsertSplicesItemsInPlaceThenResumes),
|
||||
cmocka_unit_test(test_cutsceneInsertAsLastItemEndsCutsceneNaturally),
|
||||
cmocka_unit_test(test_cutsceneInsertNestsThroughMultipleLevels),
|
||||
cmocka_unit_test(test_cutsceneInsertAssertsWhenNestingExceedsStackMax),
|
||||
cmocka_unit_test(test_cutsceneInsertDoesNotResetPauseOrInteractEntities),
|
||||
};
|
||||
|
||||
return cmocka_run_group_tests(tests, NULL, NULL);
|
||||
|
||||
Reference in New Issue
Block a user