/** * Copyright (c) 2026 Dominic Masters * * This software is released under the MIT License. * https://opensource.org/licenses/MIT */ #include "dusktest.h" #include "util/memory.h" #include "rpg/overworld/maparea.h" static uint32_t TEST_ENTER_COUNT; static uint32_t TEST_STEP_COUNT; static uint32_t TEST_EXIT_COUNT; static void testAreaCallback(entity_t *entity, const uint8_t trigger) { if(trigger == MAP_TRIGGER_ENTER) TEST_ENTER_COUNT++; else if(trigger == MAP_TRIGGER_STEP) TEST_STEP_COUNT++; else if(trigger == MAP_TRIGGER_EXIT) TEST_EXIT_COUNT++; } static void testMapAreaReset(void) { memoryZero(MAP_AREAS, sizeof(MAP_AREAS)); TEST_ENTER_COUNT = 0; TEST_STEP_COUNT = 0; TEST_EXIT_COUNT = 0; } static entity_t testEntityAt(const entitytype_t type, const worldpos_t pos) { entity_t entity; memoryZero(&entity, sizeof(entity)); entity.id = 0; entity.type = type; entity.position = pos; return entity; } static void test_mapAreaCheckEntityFiresEnterAndExit(void **state) { testMapAreaReset(); mapAreaAdd( (worldpos_t){ 0, 0, 0 }, (worldpos_t){ 5, 5, 0 }, testAreaCallback, MAP_AREA_NOTIFY_ALL, MAP_TRIGGER_ALL ); entity_t entity = testEntityAt(ENTITY_TYPE_PLAYER, (worldpos_t){ 10, 10, 0 }); mapAreaCheckEntity(&entity); assert_int_equal(TEST_ENTER_COUNT, 0); entity.position = (worldpos_t){ 2, 2, 0 }; mapAreaCheckEntity(&entity); assert_int_equal(TEST_ENTER_COUNT, 1); assert_int_equal(TEST_STEP_COUNT, 0); entity.position = (worldpos_t){ 10, 10, 0 }; mapAreaCheckEntity(&entity); assert_int_equal(TEST_EXIT_COUNT, 1); assert_int_equal(memoryGetAllocatedCount(), 0); } static void test_mapAreaCheckEntityStepFiresOncePerNewTile(void **state) { testMapAreaReset(); mapAreaAdd( (worldpos_t){ 0, 0, 0 }, (worldpos_t){ 5, 5, 0 }, testAreaCallback, MAP_AREA_NOTIFY_ALL, MAP_TRIGGER_ALL ); entity_t entity = testEntityAt(ENTITY_TYPE_PLAYER, (worldpos_t){ 1, 1, 0 }); mapAreaCheckEntity(&entity); assert_int_equal(TEST_ENTER_COUNT, 1); assert_int_equal(TEST_STEP_COUNT, 0); // Same tile, checked repeatedly (simulating multiple frames without the // entity moving) - STEP must not fire. mapAreaCheckEntity(&entity); mapAreaCheckEntity(&entity); assert_int_equal(TEST_STEP_COUNT, 0); // Moves to a new tile, still inside - STEP fires once. entity.position = (worldpos_t){ 2, 1, 0 }; mapAreaCheckEntity(&entity); assert_int_equal(TEST_STEP_COUNT, 1); // Same new tile again over multiple calls - no additional STEP. mapAreaCheckEntity(&entity); mapAreaCheckEntity(&entity); assert_int_equal(TEST_STEP_COUNT, 1); // Another new tile - STEP fires again. entity.position = (worldpos_t){ 3, 1, 0 }; mapAreaCheckEntity(&entity); assert_int_equal(TEST_STEP_COUNT, 2); assert_int_equal(TEST_ENTER_COUNT, 1); assert_int_equal(TEST_EXIT_COUNT, 0); assert_int_equal(memoryGetAllocatedCount(), 0); } static void test_mapAreaCheckEntityNotifyFlagsRespected(void **state) { testMapAreaReset(); mapAreaAdd( (worldpos_t){ 0, 0, 0 }, (worldpos_t){ 5, 5, 0 }, testAreaCallback, MAP_AREA_NOTIFY_PLAYER, MAP_TRIGGER_ALL ); entity_t npc = testEntityAt(ENTITY_TYPE_NPC, (worldpos_t){ 1, 1, 0 }); mapAreaCheckEntity(&npc); assert_int_equal(TEST_ENTER_COUNT, 0); entity_t player = testEntityAt(ENTITY_TYPE_PLAYER, (worldpos_t){ 1, 1, 0 }); mapAreaCheckEntity(&player); assert_int_equal(TEST_ENTER_COUNT, 1); assert_int_equal(memoryGetAllocatedCount(), 0); } int main(void) { const struct CMUnitTest tests[] = { cmocka_unit_test(test_mapAreaCheckEntityFiresEnterAndExit), cmocka_unit_test(test_mapAreaCheckEntityStepFiresOncePerNewTile), cmocka_unit_test(test_mapAreaCheckEntityNotifyFlagsRespected), }; return cmocka_run_group_tests(tests, NULL, NULL); }