/** * Copyright (c) 2026 Dominic Masters * * This software is released under the MIT License. * https://opensource.org/licenses/MIT */ #include "dusktest.h" #include "rpg/cutscene/cutscenesystem.h" #include "rpg/entity/entity.h" #include "time/time.h" static uint8_t callbackFired; static void recordCallback(void *userData) { callbackFired++; } // INNER is jumped into by OUTER's second item, never returning -- this is // the "nested cutscene is a one-way jump" behavior: cutsceneCutsceneStart // replaces CUTSCENE_SYSTEM.scene outright, and the callback item's effect // fires in Start, not Update, so it fires the same frame the jump happens. CUTSCENE(TEST_INNER, 0, NONE, CUTSCENE_CALLBACK(recordCallback) ); CUTSCENE(TEST_OUTER, 0, DEFAULT, CUTSCENE_WAIT(0.5f), CUTSCENE_CUTSCENE(TEST_INNER) ); static void test_cutsceneSystemStartSetsUpInitialItem(void **state) { cutsceneSystemInit(); cutsceneSystemStartCutscene(&CUTSCENE_TEST_OUTER); assert_ptr_equal(CUTSCENE_SYSTEM.scene, &CUTSCENE_TEST_OUTER); assert_int_equal(CUTSCENE_SYSTEM.currentItem, 0); assert_int_equal(CUTSCENE_SYSTEM.pause, CUTSCENE_PAUSE_DEFAULT); } static void test_cutsceneSystemNestedCutsceneIsOneWayJump(void **state) { cutsceneSystemInit(); callbackFired = 0; cutsceneSystemStartCutscene(&CUTSCENE_TEST_OUTER); TIME.delta = 0.1f; cutsceneSystemUpdate();// wait not elapsed yet assert_ptr_equal(CUTSCENE_SYSTEM.scene, &CUTSCENE_TEST_OUTER); assert_int_equal(callbackFired, 0); // The wait elapses, advancing to the nested-cutscene item, which jumps // straight into INNER and starts its first item (the callback) -- all // within this single update call. TIME.delta = 1.0f; cutsceneSystemUpdate(); assert_ptr_equal(CUTSCENE_SYSTEM.scene, &CUTSCENE_TEST_INNER); assert_int_equal(CUTSCENE_SYSTEM.currentItem, 0); assert_int_equal(callbackFired, 1); // INNER's only item (the callback) always reports complete -- one more // update ends the whole cutscene. cutsceneSystemUpdate(); assert_null(CUTSCENE_SYSTEM.scene); assert_int_equal(CUTSCENE_SYSTEM.currentItem, 0xFF); assert_int_equal(CUTSCENE_SYSTEM.pause, CUTSCENE_PAUSE_NONE); } static void test_cutsceneSystemUpdateIsNoopWithNoActiveCutscene(void **state) { cutsceneSystemInit(); cutsceneSystemUpdate();// should not crash assert_null(CUTSCENE_SYSTEM.scene); } CUTSCENE(TEST_SINGLE_WAIT, 0, NONE, CUTSCENE_WAIT(1.0f) ); static void test_cutsceneSystemStartWithSetsInteractEntities(void **state) { cutsceneSystemInit(); entityInit(&ENTITIES[0], ENTITY_TYPE_PLAYER); entityInit(&ENTITIES[1], ENTITY_TYPE_NPC); cutsceneSystemStartCutsceneWith( &CUTSCENE_TEST_SINGLE_WAIT, &ENTITIES[0], &ENTITIES[1] ); assert_ptr_equal(CUTSCENE_SYSTEM.entityInteract, &ENTITIES[0]); assert_ptr_equal(CUTSCENE_SYSTEM.entityInteracted, &ENTITIES[1]); assert_ptr_equal( cutsceneSystemGetEntity(CUTSCENE_ENTITY_INTERACT), &ENTITIES[0] ); assert_ptr_equal( cutsceneSystemGetEntity(CUTSCENE_ENTITY_INTERACTED), &ENTITIES[1] ); TIME.delta = 2.0f; cutsceneSystemUpdate();// ends the cutscene assert_null(CUTSCENE_SYSTEM.entityInteract);// reset on end assert_null(CUTSCENE_SYSTEM.entityInteracted); } static void test_cutsceneSystemGetEntitySentinelsRequireBeingSet( void **state ) { cutsceneSystemInit(); expect_assert_failure(cutsceneSystemGetEntity(CUTSCENE_ENTITY_INTERACT)); expect_assert_failure(cutsceneSystemGetEntity(CUTSCENE_ENTITY_INTERACTED)); expect_assert_failure(cutsceneSystemGetEntity(CUTSCENE_ENTITY_LAST_CREATED)); expect_assert_failure(cutsceneSystemGetEntity(CUTSCENE_ENTITY_LAST_REF)); } static void test_cutsceneSystemGetEntityDirectIndexUpdatesLastRef( void **state ) { cutsceneSystemInit(); entityInit(&ENTITIES[2], ENTITY_TYPE_NPC); entity_t *resolved = cutsceneSystemGetEntity(2); assert_ptr_equal(resolved, &ENTITIES[2]); // Resolving by direct index also updates LAST_REF. assert_ptr_equal(cutsceneSystemGetEntity(CUTSCENE_ENTITY_LAST_REF), &ENTITIES[2]); } static void test_cutsceneSystemGetAreaId(void **state) { cutsceneSystemInit(); // cutsceneSystemInit() zero-inits the field -- only actually starting a // cutscene sets it to the "nothing created yet" sentinel. CUTSCENE_SYSTEM.areaLastCreated = CUTSCENE_AREA_LAST_CREATED; expect_assert_failure(cutsceneSystemGetAreaId(CUTSCENE_AREA_LAST_CREATED)); assert_int_equal(cutsceneSystemGetAreaId(5), 5);// direct IDs pass through CUTSCENE_SYSTEM.areaLastCreated = 3; assert_int_equal(cutsceneSystemGetAreaId(CUTSCENE_AREA_LAST_CREATED), 3); } static void test_cutsceneSystemGetTextMiniId(void **state) { cutsceneSystemInit(); CUTSCENE_SYSTEM.textMiniLastCreated = CUTSCENE_TEXT_MINI_LAST_CREATED; expect_assert_failure( cutsceneSystemGetTextMiniId(CUTSCENE_TEXT_MINI_LAST_CREATED) ); assert_int_equal(cutsceneSystemGetTextMiniId(4), 4); CUTSCENE_SYSTEM.textMiniLastCreated = 1; assert_int_equal( cutsceneSystemGetTextMiniId(CUTSCENE_TEXT_MINI_LAST_CREATED), 1 ); } static void test_cutsceneSystemDisposeResetsState(void **state) { cutsceneSystemInit(); entityInit(&ENTITIES[0], ENTITY_TYPE_PLAYER); cutsceneSystemStartCutsceneWith( &CUTSCENE_TEST_SINGLE_WAIT, &ENTITIES[0], &ENTITIES[0] ); cutsceneSystemDispose(); assert_null(CUTSCENE_SYSTEM.scene); assert_int_equal(CUTSCENE_SYSTEM.currentItem, 0xFF); assert_int_equal(CUTSCENE_SYSTEM.pause, CUTSCENE_PAUSE_NONE); assert_null(CUTSCENE_SYSTEM.entityInteract); assert_null(CUTSCENE_SYSTEM.entityInteracted); } int main(int argc, char** argv) { const struct CMUnitTest tests[] = { cmocka_unit_test(test_cutsceneSystemStartSetsUpInitialItem), cmocka_unit_test(test_cutsceneSystemNestedCutsceneIsOneWayJump), cmocka_unit_test(test_cutsceneSystemUpdateIsNoopWithNoActiveCutscene), cmocka_unit_test(test_cutsceneSystemStartWithSetsInteractEntities), cmocka_unit_test(test_cutsceneSystemGetEntitySentinelsRequireBeingSet), cmocka_unit_test(test_cutsceneSystemGetEntityDirectIndexUpdatesLastRef), cmocka_unit_test(test_cutsceneSystemGetAreaId), cmocka_unit_test(test_cutsceneSystemGetTextMiniId), cmocka_unit_test(test_cutsceneSystemDisposeResetsState), }; return cmocka_run_group_tests(tests, NULL, NULL); }