diff --git a/src/dusk/rpg/cutscene/cutscene.h b/src/dusk/rpg/cutscene/cutscene.h index daec2aea..e070b1bc 100644 --- a/src/dusk/rpg/cutscene/cutscene.h +++ b/src/dusk/rpg/cutscene/cutscene.h @@ -70,6 +70,35 @@ typedef struct cutscene_s { .entityRemove = { .entityIndex = ENTITY_INDEX } \ } +#define CUTSCENE_ENTITY_ADD(TYPE, X, Y, Z) \ + { \ + .type = CUTSCENE_ITEM_TYPE_ENTITY_ADD, \ + .entityAdd = { .entityType = TYPE, .position = { X, Y, Z } } \ + } + +#define CUTSCENE_ENTITY_TURN(ENTITY_INDEX, DIRECTION) \ + { \ + .type = CUTSCENE_ITEM_TYPE_ENTITY_TURN, \ + .entityTurn = { .entityIndex = ENTITY_INDEX, .direction = DIRECTION } \ + } + +// Walks ENTITY_INDEX to stand beside TARGET_ENTITY_INDEX, offset by +// (OFFSET_X, OFFSET_Y) on the 2D plane. The destination Z is resolved +// from nearby terrain each frame, so ramps between the two entities are +// accounted for automatically. +#define CUTSCENE_ENTITY_WALK_TO_ENTITY( \ + ENTITY_INDEX, TARGET_ENTITY_INDEX, OFFSET_X, OFFSET_Y \ +) \ + { \ + .type = CUTSCENE_ITEM_TYPE_ENTITY_WALK_TO_ENTITY, \ + .entityWalkToEntity = { \ + .entityIndex = ENTITY_INDEX, \ + .targetEntityIndex = TARGET_ENTITY_INDEX, \ + .offsetX = OFFSET_X, \ + .offsetY = OFFSET_Y \ + } \ + } + #define CUTSCENE_ENTITY_TELEPORT(ENTITY_INDEX, X, Y, Z) \ { \ .type = CUTSCENE_ITEM_TYPE_ENTITY_TELEPORT, \ diff --git a/src/dusk/rpg/cutscene/cutscenesystem.c b/src/dusk/rpg/cutscene/cutscenesystem.c index e1d33cc0..dc550e3d 100644 --- a/src/dusk/rpg/cutscene/cutscenesystem.c +++ b/src/dusk/rpg/cutscene/cutscenesystem.c @@ -29,6 +29,8 @@ void cutsceneSystemStartCutsceneWith( CUTSCENE_SYSTEM.pause = cutscene->pause; CUTSCENE_SYSTEM.entityInteract = interact; CUTSCENE_SYSTEM.entityInteracted = interacted; + CUTSCENE_SYSTEM.entityLastCreated = NULL; + CUTSCENE_SYSTEM.entityLastRef = NULL; CUTSCENE_SYSTEM.currentItem = 0xFF;// Set to 0xFF so Next wraps to 0. cutsceneSystemNext(); } @@ -54,6 +56,8 @@ void cutsceneSystemNext() { CUTSCENE_SYSTEM.pause = CUTSCENE_PAUSE_NONE; CUTSCENE_SYSTEM.entityInteract = NULL; CUTSCENE_SYSTEM.entityInteracted = NULL; + CUTSCENE_SYSTEM.entityLastCreated = NULL; + CUTSCENE_SYSTEM.entityLastRef = NULL; return; } @@ -70,25 +74,42 @@ const cutsceneitem_t * cutsceneSystemGetCurrentItem() { } entity_t * cutsceneSystemGetEntity(const uint8_t entityIndex) { + entity_t *entity; + if(entityIndex == CUTSCENE_ENTITY_INTERACT) { assertNotNull( CUTSCENE_SYSTEM.entityInteract, "CUTSCENE_ENTITY_INTERACT used but no interact entity is set" ); - return CUTSCENE_SYSTEM.entityInteract; - } - if(entityIndex == CUTSCENE_ENTITY_INTERACTED) { + entity = CUTSCENE_SYSTEM.entityInteract; + } else if(entityIndex == CUTSCENE_ENTITY_INTERACTED) { assertNotNull( CUTSCENE_SYSTEM.entityInteracted, "CUTSCENE_ENTITY_INTERACTED used but no interacted entity is set" ); - return CUTSCENE_SYSTEM.entityInteracted; + entity = CUTSCENE_SYSTEM.entityInteracted; + } else if(entityIndex == CUTSCENE_ENTITY_LAST_CREATED) { + assertNotNull( + CUTSCENE_SYSTEM.entityLastCreated, + "CUTSCENE_ENTITY_LAST_CREATED used but no entity has been created" + ); + entity = CUTSCENE_SYSTEM.entityLastCreated; + } else if(entityIndex == CUTSCENE_ENTITY_LAST_REF) { + assertNotNull( + CUTSCENE_SYSTEM.entityLastRef, + "CUTSCENE_ENTITY_LAST_REF used but no entity has been referenced" + ); + entity = CUTSCENE_SYSTEM.entityLastRef; + } else { + assertTrue( + entityIndex < ENTITY_COUNT, + "Entity index is out of range" + ); + entity = &ENTITIES[entityIndex]; } - assertTrue( - entityIndex < ENTITY_COUNT, - "Entity index is out of range" - ); - return &ENTITIES[entityIndex]; + + CUTSCENE_SYSTEM.entityLastRef = entity; + return entity; } void cutsceneSystemDispose() { @@ -97,4 +118,6 @@ void cutsceneSystemDispose() { CUTSCENE_SYSTEM.pause = CUTSCENE_PAUSE_NONE; CUTSCENE_SYSTEM.entityInteract = NULL; CUTSCENE_SYSTEM.entityInteracted = NULL; + CUTSCENE_SYSTEM.entityLastCreated = NULL; + CUTSCENE_SYSTEM.entityLastRef = NULL; } diff --git a/src/dusk/rpg/cutscene/cutscenesystem.h b/src/dusk/rpg/cutscene/cutscenesystem.h index b4367f0f..43223971 100644 --- a/src/dusk/rpg/cutscene/cutscenesystem.h +++ b/src/dusk/rpg/cutscene/cutscenesystem.h @@ -10,8 +10,10 @@ typedef struct entity_s entity_t; -#define CUTSCENE_ENTITY_INTERACT ((uint8_t)0xFE) -#define CUTSCENE_ENTITY_INTERACTED ((uint8_t)0xFD) +#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) typedef struct { const cutscene_t *scene; @@ -19,6 +21,8 @@ typedef struct { cutscenepause_t pause; entity_t *entityInteract; entity_t *entityInteracted; + entity_t *entityLastCreated; + entity_t *entityLastRef; // Data (used by the current item). cutsceneitemdata_t data; @@ -53,7 +57,9 @@ void cutsceneSystemStartCutsceneWith( /** * Resolves a raw entity index (or sentinel) to an entity pointer. - * Handles CUTSCENE_ENTITY_INTERACT and CUTSCENE_ENTITY_INTERACTED. + * Handles CUTSCENE_ENTITY_INTERACT, CUTSCENE_ENTITY_INTERACTED, + * CUTSCENE_ENTITY_LAST_CREATED and CUTSCENE_ENTITY_LAST_REF. + * Updates CUTSCENE_SYSTEM.entityLastRef to the resolved entity. * Asserts the resolved entity is within bounds. * * @param entityIndex Raw entity index or sentinel value. diff --git a/src/dusk/rpg/cutscene/item/cutsceneitem.c b/src/dusk/rpg/cutscene/item/cutsceneitem.c index 5b9e186f..2498305b 100644 --- a/src/dusk/rpg/cutscene/item/cutsceneitem.c +++ b/src/dusk/rpg/cutscene/item/cutsceneitem.c @@ -53,6 +53,18 @@ void cutsceneItemStart(const cutsceneitem_t *item, cutsceneitemdata_t *data) { cutsceneEntityRemoveStart(item, data); break; + case CUTSCENE_ITEM_TYPE_ENTITY_ADD: + cutsceneEntityAddStart(item, data); + break; + + case CUTSCENE_ITEM_TYPE_ENTITY_TURN: + cutsceneEntityTurnStart(item, data); + break; + + case CUTSCENE_ITEM_TYPE_ENTITY_WALK_TO_ENTITY: + cutsceneEntityWalkToEntityStart(item, data); + break; + default: break; } @@ -90,6 +102,15 @@ bool_t cutsceneItemUpdate(const cutsceneitem_t *item, cutsceneitemdata_t *data) case CUTSCENE_ITEM_TYPE_ENTITY_REMOVE: return cutsceneEntityRemoveUpdate(item, data); + case CUTSCENE_ITEM_TYPE_ENTITY_ADD: + return cutsceneEntityAddUpdate(item, data); + + case CUTSCENE_ITEM_TYPE_ENTITY_TURN: + return cutsceneEntityTurnUpdate(item, data); + + case CUTSCENE_ITEM_TYPE_ENTITY_WALK_TO_ENTITY: + return cutsceneEntityWalkToEntityUpdate(item, data); + default: return false; } diff --git a/src/dusk/rpg/cutscene/item/cutsceneitem.h b/src/dusk/rpg/cutscene/item/cutsceneitem.h index c6912a97..0d68daf4 100644 --- a/src/dusk/rpg/cutscene/item/cutsceneitem.h +++ b/src/dusk/rpg/cutscene/item/cutsceneitem.h @@ -13,6 +13,9 @@ #include "entity/cutsceneentityteleport.h" #include "entity/cutsceneentitywalkto.h" #include "entity/cutsceneentityremove.h" +#include "entity/cutsceneentityadd.h" +#include "entity/cutsceneentityturn.h" +#include "entity/cutsceneentitywalktoentity.h" #include "ui/cutscenetext.h" #include "ui/cutscenefade.h" #include "item/cutsceneitemgive.h" @@ -31,7 +34,10 @@ typedef enum { CUTSCENE_ITEM_TYPE_SET_PAUSE, CUTSCENE_ITEM_TYPE_CONCURRENT, CUTSCENE_ITEM_TYPE_ITEM_GIVE, - CUTSCENE_ITEM_TYPE_ENTITY_REMOVE + CUTSCENE_ITEM_TYPE_ENTITY_REMOVE, + CUTSCENE_ITEM_TYPE_ENTITY_ADD, + CUTSCENE_ITEM_TYPE_ENTITY_TURN, + CUTSCENE_ITEM_TYPE_ENTITY_WALK_TO_ENTITY } cutsceneitemtype_t; struct cutsceneitem_s { @@ -49,6 +55,9 @@ struct cutsceneitem_s { cutsceneconcurrent_t concurrent; cutsceneitemgive_t itemGive; cutsceneentityremove_t entityRemove; + cutsceneentityadd_t entityAdd; + cutsceneentityturn_t entityTurn; + cutsceneentitywalktoentity_t entityWalkToEntity; }; }; diff --git a/src/dusk/rpg/cutscene/item/entity/CMakeLists.txt b/src/dusk/rpg/cutscene/item/entity/CMakeLists.txt index 27af3e17..e6dfbd2d 100644 --- a/src/dusk/rpg/cutscene/item/entity/CMakeLists.txt +++ b/src/dusk/rpg/cutscene/item/entity/CMakeLists.txt @@ -8,4 +8,7 @@ target_sources(${DUSK_LIBRARY_TARGET_NAME} cutsceneentityteleport.c cutsceneentitywalkto.c cutsceneentityremove.c + cutsceneentityadd.c + cutsceneentityturn.c + cutsceneentitywalktoentity.c ) diff --git a/src/dusk/rpg/cutscene/item/entity/cutsceneentityadd.c b/src/dusk/rpg/cutscene/item/entity/cutsceneentityadd.c new file mode 100644 index 00000000..481aab77 --- /dev/null +++ b/src/dusk/rpg/cutscene/item/entity/cutsceneentityadd.c @@ -0,0 +1,39 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#include "rpg/cutscene/item/cutsceneitem.h" +#include "rpg/cutscene/cutscenesystem.h" +#include "rpg/entity/entity.h" +#include "rpg/overworld/map.h" +#include "assert/assert.h" + +void cutsceneEntityAddStart( + const cutsceneitem_t *item, + cutsceneitemdata_t *data +) { + uint8_t entIndex = entityGetAvailable(); + assertTrue(entIndex != 0xFF, "No available entity slots for CUTSCENE_ENTITY_ADD"); + + entity_t *entity = &ENTITIES[entIndex]; + entityInit(entity, item->entityAdd.entityType); + entityPositionSet(entity, item->entityAdd.position); + + chunkpos_t cp; + worldPosToChunkPos(&entity->position, &cp); + chunkindex_t ci = mapGetChunkIndexAt(cp); + if(ci != -1) entitySetChunk(entity, (uint8_t)ci); + + CUTSCENE_SYSTEM.entityLastCreated = entity; + CUTSCENE_SYSTEM.entityLastRef = entity; +} + +bool_t cutsceneEntityAddUpdate( + const cutsceneitem_t *item, + cutsceneitemdata_t *data +) { + return true; +} diff --git a/src/dusk/rpg/cutscene/item/entity/cutsceneentityadd.h b/src/dusk/rpg/cutscene/item/entity/cutsceneentityadd.h new file mode 100644 index 00000000..5e0944b1 --- /dev/null +++ b/src/dusk/rpg/cutscene/item/entity/cutsceneentityadd.h @@ -0,0 +1,41 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#pragma once +#include "rpg/entity/entitytype.h" +#include "rpg/overworld/worldpos.h" + +typedef struct cutsceneitem_s cutsceneitem_t; +typedef union cutsceneitemdata_u cutsceneitemdata_t; + +typedef struct { + entitytype_t entityType; + worldpos_t position; +} cutsceneentityadd_t; + +/** + * Starts an entity add step (spawns the entity into the world immediately). + * + * @param item The cutscene item. + * @param data Runtime data storage. + */ +void cutsceneEntityAddStart( + const cutsceneitem_t *item, + cutsceneitemdata_t *data +); + +/** + * Updates an entity add step (always completes immediately). + * + * @param item The cutscene item. + * @param data Runtime data storage. + * @returns true always. + */ +bool_t cutsceneEntityAddUpdate( + const cutsceneitem_t *item, + cutsceneitemdata_t *data +); diff --git a/src/dusk/rpg/cutscene/item/entity/cutsceneentityturn.c b/src/dusk/rpg/cutscene/item/entity/cutsceneentityturn.c new file mode 100644 index 00000000..707389b4 --- /dev/null +++ b/src/dusk/rpg/cutscene/item/entity/cutsceneentityturn.c @@ -0,0 +1,30 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#include "rpg/cutscene/item/cutsceneitem.h" +#include "rpg/cutscene/cutscenesystem.h" +#include "rpg/entity/entity.h" + +void cutsceneEntityTurnStart( + const cutsceneitem_t *item, + cutsceneitemdata_t *data +) { +} + +bool_t cutsceneEntityTurnUpdate( + const cutsceneitem_t *item, + cutsceneitemdata_t *data +) { + entity_t *entity = cutsceneSystemGetEntity(item->entityTurn.entityIndex); + if( + entity->direction == item->entityTurn.direction && + entity->animation == ENTITY_ANIM_IDLE + ) return true; + + entityTurn(entity, item->entityTurn.direction); + return false; +} diff --git a/src/dusk/rpg/cutscene/item/entity/cutsceneentityturn.h b/src/dusk/rpg/cutscene/item/entity/cutsceneentityturn.h new file mode 100644 index 00000000..a14bb31b --- /dev/null +++ b/src/dusk/rpg/cutscene/item/entity/cutsceneentityturn.h @@ -0,0 +1,42 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#pragma once +#include "rpg/entity/entitydir.h" + +typedef struct cutsceneitem_s cutsceneitem_t; +typedef union cutsceneitemdata_u cutsceneitemdata_t; + +typedef struct { + uint8_t entityIndex; + entitydir_t direction; +} cutsceneentityturn_t; + +/** + * Starts an entity turn step. The turn itself is driven from Update, since + * the entity may still be finishing a previous action. + * + * @param item The cutscene item. + * @param data Runtime data storage. + */ +void cutsceneEntityTurnStart( + const cutsceneitem_t *item, + cutsceneitemdata_t *data +); + +/** + * Updates an entity turn step, retrying entityTurn until it takes effect + * and its animation completes. + * + * @param item The cutscene item. + * @param data Runtime data storage. + * @returns true once the entity is idle and facing the target direction. + */ +bool_t cutsceneEntityTurnUpdate( + const cutsceneitem_t *item, + cutsceneitemdata_t *data +); diff --git a/src/dusk/rpg/cutscene/item/entity/cutsceneentitywalktoentity.c b/src/dusk/rpg/cutscene/item/entity/cutsceneentitywalktoentity.c new file mode 100644 index 00000000..2b695545 --- /dev/null +++ b/src/dusk/rpg/cutscene/item/entity/cutsceneentitywalktoentity.c @@ -0,0 +1,41 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#include "rpg/cutscene/item/cutsceneitem.h" +#include "rpg/cutscene/cutscenesystem.h" +#include "rpg/entity/entity.h" +#include "rpg/entity/entitypathstep.h" +#include "rpg/overworld/map.h" + +void cutsceneEntityWalkToEntityStart( + const cutsceneitem_t *item, + cutsceneitemdata_t *data +) { +} + +bool_t cutsceneEntityWalkToEntityUpdate( + const cutsceneitem_t *item, + cutsceneitemdata_t *data +) { + entity_t *entity = cutsceneSystemGetEntity( + item->entityWalkToEntity.entityIndex + ); + entity_t *target = cutsceneSystemGetEntity( + item->entityWalkToEntity.targetEntityIndex + ); + + worldpos_t dest = { + .x = (worldunit_t)(target->position.x + item->entityWalkToEntity.offsetX), + .y = (worldunit_t)(target->position.y + item->entityWalkToEntity.offsetY), + .z = target->position.z + }; + + worldunit_t z; + if(mapGetWalkableZNear(dest.x, dest.y, target->position.z, &z)) dest.z = z; + + return entityPathStep(entity, dest, true); +} diff --git a/src/dusk/rpg/cutscene/item/entity/cutsceneentitywalktoentity.h b/src/dusk/rpg/cutscene/item/entity/cutsceneentitywalktoentity.h new file mode 100644 index 00000000..0014ed4b --- /dev/null +++ b/src/dusk/rpg/cutscene/item/entity/cutsceneentitywalktoentity.h @@ -0,0 +1,46 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#pragma once +#include "rpg/overworld/worldpos.h" + +typedef struct cutsceneitem_s cutsceneitem_t; +typedef union cutsceneitemdata_u cutsceneitemdata_t; + +typedef struct { + uint8_t entityIndex; + uint8_t targetEntityIndex; + worldunit_t offsetX; + worldunit_t offsetY; +} cutsceneentitywalktoentity_t; + +/** + * Starts an entity walk-to-entity item. No setup is needed, the destination + * is recomputed from the target's live position every Update. + * + * @param item The cutscene item. + * @param data Runtime data storage. + */ +void cutsceneEntityWalkToEntityStart( + const cutsceneitem_t *item, + cutsceneitemdata_t *data +); + +/** + * Updates an entity walk-to-entity item. Re-reads the target entity's + * current position each frame, applies the X/Y offset, resolves the + * destination Z from nearby terrain (to account for ramps), and steps + * the entity toward it. + * + * @param item The cutscene item. + * @param data Runtime data storage. + * @returns true once the entity has reached the target's side. + */ +bool_t cutsceneEntityWalkToEntityUpdate( + const cutsceneitem_t *item, + cutsceneitemdata_t *data +); diff --git a/src/dusk/rpg/cutscene/scene/testcutscene.h b/src/dusk/rpg/cutscene/scene/testcutscene.h index 7227c340..50bda860 100755 --- a/src/dusk/rpg/cutscene/scene/testcutscene.h +++ b/src/dusk/rpg/cutscene/scene/testcutscene.h @@ -15,11 +15,13 @@ CUTSCENE(TEST_ONE, DEFAULT, CUTSCENE(TEST_TWO, DEFAULT, CUTSCENE_TEXT("Test Two."), - CUTSCENE_CONCURRENT( - CUTSCENE_ENTITY_WALK_TO(CUTSCENE_ENTITY_INTERACT, 4, 4, 0), - CUTSCENE_ENTITY_WALK_TO(CUTSCENE_ENTITY_INTERACTED, 8, 2, 0), - ), - CUTSCENE_ITEM_GIVE(ITEM_ID_POTATO, 3), - CUTSCENE_ENTITY_REMOVE(CUTSCENE_ENTITY_INTERACT), + CUTSCENE_ENTITY_ADD(ENTITY_TYPE_NPC, 4, 4, 0), + CUTSCENE_ENTITY_WALK_TO(CUTSCENE_ENTITY_LAST_CREATED, 8, 2, 0), + // CUTSCENE_CONCURRENT( + // CUTSCENE_ENTITY_WALK_TO(CUTSCENE_ENTITY_INTERACT, 4, 4, 0), + // CUTSCENE_ENTITY_WALK_TO(CUTSCENE_ENTITY_INTERACTED, 8, 2, 0), + // ), + // CUTSCENE_ITEM_GIVE(ITEM_ID_POTATO, 3), + // CUTSCENE_ENTITY_REMOVE(CUTSCENE_ENTITY_INTERACT), CUTSCENE_TEXT("Done."), ); \ No newline at end of file diff --git a/src/dusk/rpg/entity/player.h b/src/dusk/rpg/entity/player.h index a9606a44..13e369d1 100644 --- a/src/dusk/rpg/entity/player.h +++ b/src/dusk/rpg/entity/player.h @@ -7,6 +7,7 @@ #pragma once #include "input/input.h" +#include "entitydir.h" typedef struct entity_s entity_t; diff --git a/src/dusk/rpg/overworld/map.c b/src/dusk/rpg/overworld/map.c index c81bd1d0..420387d4 100644 --- a/src/dusk/rpg/overworld/map.c +++ b/src/dusk/rpg/overworld/map.c @@ -242,6 +242,27 @@ tile_t mapGetTile(const worldpos_t position) { return chunk->tiles[tileIndex]; } +bool_t mapGetWalkableZNear( + const worldunit_t x, + const worldunit_t y, + const worldunit_t nearZ, + worldunit_t *outZ +) { + assertNotNull(outZ, "Output Z pointer cannot be NULL"); + + const worldunit_t candidates[] = { + nearZ, (worldunit_t)(nearZ + 1), (worldunit_t)(nearZ - 1) + }; + for(uint8_t i = 0; i < 3; i++) { + const worldpos_t pos = { x, y, candidates[i] }; + if(!tileIsWalkable(mapGetTile(pos))) continue; + *outZ = candidates[i]; + return true; + } + + return false; +} + void mapRebuildChunkOrder() { memoryZero(MAP.chunkOrder, sizeof(MAP.chunkOrder)); for(chunkindex_t i = 0; i < MAP_LOADED_CHUNK_COUNT; i++) { diff --git a/src/dusk/rpg/overworld/map.h b/src/dusk/rpg/overworld/map.h index 10d54460..5a4cf0c1 100644 --- a/src/dusk/rpg/overworld/map.h +++ b/src/dusk/rpg/overworld/map.h @@ -128,8 +128,26 @@ chunk_t * mapGetChunk(const uint8_t chunkIndex); /** * Gets the tile at the given world position. - * + * * @param position The world position. * @return The tile at that position, or TILE_NULL if the chunk is unloaded. */ -tile_t mapGetTile(const worldpos_t position); \ No newline at end of file +tile_t mapGetTile(const worldpos_t position); + +/** + * Finds the closest walkable Z layer to nearZ at the given X/Y. Checks + * nearZ first, then nearZ + 1, then nearZ - 1, since ramps only ever + * change height by one Z layer between adjacent tiles. + * + * @param x The world X coordinate to check. + * @param y The world Y coordinate to check. + * @param nearZ The reference Z layer to search outward from. + * @param outZ Output pointer, set to the resolved Z layer on success. + * @return true if a walkable tile was found, false otherwise. + */ +bool_t mapGetWalkableZNear( + const worldunit_t x, + const worldunit_t y, + const worldunit_t nearZ, + worldunit_t *outZ +); \ No newline at end of file