200 lines
5.7 KiB
C
200 lines
5.7 KiB
C
/**
|
|
* 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;
|
|
} |