/** * Copyright (c) 2025 Dominic Masters * * This software is released under the MIT License. * https://opensource.org/licenses/MIT */ #include "entity.h" #include "assert/assert.h" #include "util/memory.h" #include "time/time.h" #include "util/math.h" #include "rpg/cutscene/cutscenemode.h" #include "rpg/world/map.h" entity_t ENTITIES[ENTITY_COUNT]; void entityInit(entity_t *entity, const entitytype_t type) { assertNotNull(entity, "Entity pointer cannot be NULL"); assertTrue(type < ENTITY_TYPE_COUNT, "Invalid entity type"); assertTrue(type != ENTITY_TYPE_NULL, "Cannot have NULL entity type"); assertTrue( entity >= ENTITIES && entity < ENTITIES + ENTITY_COUNT, "Entity pointer is out of bounds" ); memoryZero(entity, sizeof(entity_t)); entity->id = (uint8_t)(entity - ENTITIES); entity->type = type; if(ENTITY_CALLBACKS[type].init != NULL) ENTITY_CALLBACKS[type].init(entity); } void entityUpdate(entity_t *entity) { assertNotNull(entity, "Entity pointer cannot be NULL"); assertTrue(entity->type < ENTITY_TYPE_COUNT, "Invalid entity type"); assertTrue(entity->type != ENTITY_TYPE_NULL, "Cannot have NULL entity type"); // What state is the entity in? if(entity->animation != ENTITY_ANIM_IDLE) { // Entity is mid animation, tick it (down). entity->animTime -= TIME.delta; if(entity->animTime <= 0) { entity->animation = ENTITY_ANIM_IDLE; entity->animTime = 0; } return; } // Movement code. if( cutsceneModeIsInputAllowed() && ENTITY_CALLBACKS[entity->type].movement != NULL ) { ENTITY_CALLBACKS[entity->type].movement(entity); } } void entityTurn(entity_t *entity, const entitydir_t direction) { entity->direction = direction; entity->animation = ENTITY_ANIM_TURN; entity->animTime = ENTITY_ANIM_TURN_DURATION; } void entityWalk(entity_t *entity, const entitydir_t direction) { // TODO: Animation, delay, etc. entity->direction = direction; // Where are we moving? worldpos_t newPos = entity->position; { worldunits_t relX, relY; entityDirGetRelative(direction, &relX, &relY); newPos.x += relX; newPos.y += relY; } // Get tile under foot tile_t tileCurrent = mapGetTile(entity->position); tile_t tileNew = mapGetTile(newPos); bool_t fall = false; bool_t raise = false; // Are we walking up a ramp? if( tileIsRamp(tileCurrent) && ( // Can only walk UP the direction the ramp faces. (direction+TILE_SHAPE_RAMP_SOUTH) == tileCurrent || // If diagonal ramp, can go up one of two ways only. ( ( tileCurrent == TILE_SHAPE_RAMP_SOUTHEAST && (direction == ENTITY_DIR_SOUTH || direction == ENTITY_DIR_EAST) ) || ( tileCurrent == TILE_SHAPE_RAMP_SOUTHWEST && (direction == ENTITY_DIR_SOUTH || direction == ENTITY_DIR_WEST) ) || ( tileCurrent == TILE_SHAPE_RAMP_NORTHEAST && (direction == ENTITY_DIR_NORTH || direction == ENTITY_DIR_EAST) ) || ( tileCurrent == TILE_SHAPE_RAMP_NORTHWEST && (direction == ENTITY_DIR_NORTH || direction == ENTITY_DIR_WEST) ) ) // Must be able to walk up. ) && newPos.z < (MAP_CHUNK_DEPTH - 1) ) { tileNew = TILE_SHAPE_NULL;// Force check for ramp above. worldpos_t abovePos = newPos; abovePos.z += 1; tile_t tileAbove = mapGetTile(abovePos); if(tileAbove != TILE_SHAPE_NULL && tileIsWalkable(tileAbove)) { // We can go up the ramp. raise = true; } } else if(tileNew == TILE_SHAPE_NULL && newPos.z > 0) { // Falling down? worldpos_t belowPos = newPos; belowPos.z -= 1; tile_t tileBelow = mapGetTile(belowPos); if( tileBelow != TILE_SHAPE_NULL && tileIsRamp(tileBelow) && ( // This handles regular cardinal ramps (entityDirGetOpposite(direction)+TILE_SHAPE_RAMP_SOUTH) == tileBelow || // This handles diagonal ramps ( ( tileBelow == TILE_SHAPE_RAMP_SOUTHEAST && (direction == ENTITY_DIR_NORTH || direction == ENTITY_DIR_WEST) ) || ( tileBelow == TILE_SHAPE_RAMP_SOUTHWEST && (direction == ENTITY_DIR_NORTH || direction == ENTITY_DIR_EAST) ) || ( tileBelow == TILE_SHAPE_RAMP_NORTHEAST && (direction == ENTITY_DIR_SOUTH || direction == ENTITY_DIR_WEST) ) || ( tileBelow == TILE_SHAPE_RAMP_NORTHWEST && (direction == ENTITY_DIR_SOUTH || direction == ENTITY_DIR_EAST) ) ) ) ) { // We will fall to this tile. fall = true; } } // Can we walk here? if(!raise && !fall && !tileIsWalkable(tileNew)) return;// Blocked // Entity in way? entity_t *other = ENTITIES; do { if(other == entity) continue; if(other->type == ENTITY_TYPE_NULL) continue; if(!worldPosIsEqual(other->position, newPos)) continue; return;// Blocked } while(++other, other < &ENTITIES[ENTITY_COUNT]); entity->lastPosition = entity->position; entity->position = newPos; entity->animation = ENTITY_ANIM_WALK; entity->animTime = ENTITY_ANIM_WALK_DURATION;// TODO: Running vs walking if(raise) { entity->position.z += 1; } else if(fall) { entity->position.z -= 1; } } entity_t * entityGetAt(const worldpos_t position) { entity_t *ent = ENTITIES; do { if(ent->type == ENTITY_TYPE_NULL) continue; if(!worldPosIsEqual(ent->position, position)) continue; return ent; } while(++ent, ent < &ENTITIES[ENTITY_COUNT]); return NULL; } uint8_t entityGetAvailable() { entity_t *ent = ENTITIES; do { if(ent->type == ENTITY_TYPE_NULL) return ent - ENTITIES; } while(++ent, ent < &ENTITIES[ENTITY_COUNT]); return 0xFF; }