From a34831aa02ed3b576ad683d2c2a11741c7569bad Mon Sep 17 00:00:00 2001 From: Dominic Masters Date: Tue, 30 Jun 2026 21:23:34 -0500 Subject: [PATCH] Add entity turn lockout --- src/dusk/asset/loader/dmf/assetmeshloader.c | 8 ++- src/dusk/rpg/entity/anim/entityanim.c | 11 ++++ src/dusk/rpg/entity/anim/entityanimturn.h | 2 +- src/dusk/rpg/entity/anim/entityanimwalk.h | 3 +- src/dusk/rpg/entity/entity.c | 3 +- src/dusk/rpg/entity/entity.h | 1 + src/dusk/rpg/entity/player.c | 57 +++++++++++++-------- src/dusk/rpg/entity/player.h | 10 +++- 8 files changed, 70 insertions(+), 25 deletions(-) diff --git a/src/dusk/asset/loader/dmf/assetmeshloader.c b/src/dusk/asset/loader/dmf/assetmeshloader.c index db8a80a4..a4c5bcaf 100644 --- a/src/dusk/asset/loader/dmf/assetmeshloader.c +++ b/src/dusk/asset/loader/dmf/assetmeshloader.c @@ -113,6 +113,12 @@ errorret_t assetMeshLoaderSync(assetloading_t *loading) { errorChain(ret); } + #ifndef DUSK_OPENGL_LEGACY + // VBO owns the data now; CPU copy is no longer needed. + memoryFree(out->vertices); + out->vertices = NULL; + #endif + loading->entry->state = ASSET_ENTRY_STATE_LOADED; errorOk(); } @@ -125,7 +131,7 @@ errorret_t assetMeshDispose(assetentry_t *entry) { assetmeshoutput_t *out = &entry->data.mesh; if(out->vertices != NULL) { errorChain(meshDispose(&out->mesh)); - // memoryFree(out->vertices); + memoryFree(out->vertices); out->vertices = NULL; } errorOk(); diff --git a/src/dusk/rpg/entity/anim/entityanim.c b/src/dusk/rpg/entity/anim/entityanim.c index b22ceaa5..114fc526 100644 --- a/src/dusk/rpg/entity/anim/entityanim.c +++ b/src/dusk/rpg/entity/anim/entityanim.c @@ -9,6 +9,7 @@ #include "rpg/overworld/map.h" #include "rpg/overworld/tile.h" #include "time/time.h" +#include "entityanimwalk.h" const entityanimcallback_t ENTITY_ANIM_CALLBACKS[ENTITY_ANIM_COUNT] = { [ENTITY_ANIM_IDLE] = { entityAnimIdleUpdate }, @@ -25,9 +26,19 @@ void entityAnimUpdate(entity_t *entity) { if(entity->animation != ENTITY_ANIM_IDLE) { entity->animTime -= TIME.delta; if(entity->animTime <= 0) { + if( + entity->animation == ENTITY_ANIM_WALK || + entity->animation == ENTITY_ANIM_RUN + ) { + entity->walkEndCooldown = ENTITY_ANIM_WALK_TURN_COOLDOWN; + } entity->animation = ENTITY_ANIM_IDLE; entity->animTime = 0; } } + if(entity->walkEndCooldown > 0) { + entity->walkEndCooldown -= TIME.delta; + if(entity->walkEndCooldown < 0) entity->walkEndCooldown = 0; + } ENTITY_ANIM_CALLBACKS[entity->animation].update(entity); } diff --git a/src/dusk/rpg/entity/anim/entityanimturn.h b/src/dusk/rpg/entity/anim/entityanimturn.h index aa765dfd..33368d52 100644 --- a/src/dusk/rpg/entity/anim/entityanimturn.h +++ b/src/dusk/rpg/entity/anim/entityanimturn.h @@ -11,7 +11,7 @@ typedef struct entity_s entity_t; -#define ENTITY_ANIM_TURN_DURATION TIME_TICKS_TO_TIME(2) +#define ENTITY_ANIM_TURN_DURATION TIME_TICKS_TO_TIME(4) /** * Updates render position for the turn animation state. diff --git a/src/dusk/rpg/entity/anim/entityanimwalk.h b/src/dusk/rpg/entity/anim/entityanimwalk.h index 608a46ff..5ab12585 100644 --- a/src/dusk/rpg/entity/anim/entityanimwalk.h +++ b/src/dusk/rpg/entity/anim/entityanimwalk.h @@ -11,7 +11,8 @@ typedef struct entity_s entity_t; -#define ENTITY_ANIM_WALK_DURATION TIME_TICKS_TO_TIME(10) +#define ENTITY_ANIM_WALK_DURATION TIME_TICKS_TO_TIME(12) +#define ENTITY_ANIM_WALK_TURN_COOLDOWN TIME_TICKS_TO_TIME(4) /** * Updates render position for the walk animation state. diff --git a/src/dusk/rpg/entity/entity.c b/src/dusk/rpg/entity/entity.c index c806ea10..4988e2f0 100644 --- a/src/dusk/rpg/entity/entity.c +++ b/src/dusk/rpg/entity/entity.c @@ -52,7 +52,8 @@ void entityUpdate(entity_t *entity) { } bool_t entityCanTurn(entity_t *entity) { - return entity->animation == ENTITY_ANIM_IDLE; + return entity->animation == ENTITY_ANIM_IDLE && + entity->walkEndCooldown <= 0; } bool_t entityCanWalk(entity_t *entity) { diff --git a/src/dusk/rpg/entity/entity.h b/src/dusk/rpg/entity/entity.h index 6e8f9085..787d8aa5 100644 --- a/src/dusk/rpg/entity/entity.h +++ b/src/dusk/rpg/entity/entity.h @@ -27,6 +27,7 @@ typedef struct entity_s { entityanim_t animation; float_t animTime; + float_t walkEndCooldown; entityinteract_t interact; diff --git a/src/dusk/rpg/entity/player.c b/src/dusk/rpg/entity/player.c index 521b9e59..a041377b 100644 --- a/src/dusk/rpg/entity/player.c +++ b/src/dusk/rpg/entity/player.c @@ -17,6 +17,10 @@ void playerInit(entity_t *entity) { assertNotNull(entity, "Entity pointer cannot be NULL"); } +bool_t playerCanInteract(entity_t *entity) { + return entity->animation == ENTITY_ANIM_IDLE; +} + void playerInput(entity_t *entity) { assertNotNull(entity, "Entity pointer cannot be NULL"); @@ -33,37 +37,51 @@ void playerInput(entity_t *entity) { // Can player act? if(UI_FOCUS.count > 0) return; - // Turn - only if not already holding the direction we face - const playerinputdirmap_t *dirMap = PLAYER_INPUT_DIR_MAP; - bool_t holdingFaced = false; + // Determine direction player may be trying to face/go. + const playerinputdirmap_t *dirMap; + + // First pass, we want to prefer a button relative to what the player is + // already facing, e.g. if they hold DOWN and start moving down, then tap + // right we want to prefer down over right. + + // Determine current dirmap based on facing direction. + dirMap = PLAYER_INPUT_DIR_MAP; do { - if(!inputIsDown(dirMap->action)) continue; - if(entity->direction != dirMap->direction) continue; - holdingFaced = true; + if(dirMap->direction != entity->direction) continue; break; } while((++dirMap)->action != 0xFF); - if(!holdingFaced) { + // Are we holding that button? + if(!inputIsDown(dirMap->action)) { + // No, so we need to check all directions. dirMap = PLAYER_INPUT_DIR_MAP; do { if(!inputIsDown(dirMap->action)) continue; - if(entity->direction == dirMap->direction) continue; - return entityTurn(entity, dirMap->direction); + break; } while((++dirMap)->action != 0xFF); } - // Walk / Run - bool_t running = inputIsDown(INPUT_ACTION_CANCEL); - dirMap = PLAYER_INPUT_DIR_MAP; - do { - if(!inputIsDown(dirMap->action)) continue; - if(entity->direction != dirMap->direction) continue; - if(running) return entityRun(entity, dirMap->direction); - return entityWalk(entity, dirMap->direction); - } while((++dirMap)->action != 0xFF); + // Trying to direct? + if(dirMap->action != 0xFF) { + // Is the player attempting to turn? That is, is the player idle and has the + // turn lockout passed? + if(entityCanTurn(entity)) { + if(entity->direction != dirMap->direction) { + entityTurn(entity, dirMap->direction); + return; + } + } + + bool_t running = inputIsDown(INPUT_ACTION_CANCEL); + if(running && entityCanRun(entity)) { + entityRun(entity, dirMap->direction); + } else if(entityCanWalk(entity)) { + entityWalk(entity, dirMap->direction); + } + } // Interaction - if(inputPressed(INPUT_ACTION_ACCEPT)) { + if(inputPressed(INPUT_ACTION_ACCEPT) && playerCanInteract(entity)) { worldunit_t x, y, z; { worldunits_t relX, relY; @@ -77,5 +95,4 @@ void playerInput(entity_t *entity) { if(target == NULL) return; entityInteractWith(entity, target); } - } \ No newline at end of file diff --git a/src/dusk/rpg/entity/player.h b/src/dusk/rpg/entity/player.h index 8c10f31c..a9606a44 100644 --- a/src/dusk/rpg/entity/player.h +++ b/src/dusk/rpg/entity/player.h @@ -35,9 +35,17 @@ static const playerinputdirmap_t PLAYER_INPUT_DIR_MAP[] = { */ void playerInit(entity_t *entity); +/** + * Returns true if the player entity is in a state where it can interact. + * + * @param entity Pointer to the player entity to check. + * @returns True if the entity can interact. + */ +bool_t playerCanInteract(entity_t *entity); + /** * Handles movement logic for the player entity. - * + * * @param entity Pointer to the player entity structure. */ void playerInput(entity_t *entity); \ No newline at end of file