Unify entity update to only use state switches

Remove the use of beginAttack and endAttack in the entity update loop and
delegate all changes to entity through state switching. This
consolidates the flow of data in the loop more consistently by going
through one master function. This also minimises the number of
entry-points to functions that will update an entity states- for ease of
comprehension.
This commit is contained in:
Doyle Thai 2016-07-24 01:22:59 +10:00
parent a43102ee26
commit 8fc895e584

View File

@ -679,102 +679,6 @@ INTERNAL inline void resetEntityState(World *world, Entity *entity)
entity->stats->entityIdToAttack = ENTITY_NULL_ID; entity->stats->entityIdToAttack = ENTITY_NULL_ID;
} }
INTERNAL void entityStateSwitch(World *world, Entity *entity,
enum EntityState newState)
{
#ifdef DENGINE_DEBUG
ASSERT(world && entity)
ASSERT(entity->type == entitytype_mob || entity->type == entitytype_hero)
#endif
if (entity->state == newState) return;
switch(entity->state)
{
case entitystate_idle:
switch (newState)
{
case entitystate_battle:
updateWorldBattleEntities(world, entity, ENTITY_IN_BATTLE);
entity->stats->entityIdToAttack =
findBestEntityToAttack(world, *entity);
break;
// TODO(doyle): Corner case- if move out of range and entity has
// switched to idle mode, we reach the attacker entity and they continue
// attacking it since there's no check before attack if entity is idle
// or not (i.e. has moved out of frame last frame).
case entitystate_dead:
setActiveEntityAnim(entity, animlist_hero_idle);
entity->stats->busyDuration = 0;
entity->stats->actionTimer = entity->stats->actionRate;
entity->stats->queuedAttack = entityattack_invalid;
entity->stats->entityIdToAttack = ENTITY_NULL_ID;
break;
case entitystate_attack:
default:
#ifdef DENGINE_DEBUG
ASSERT(INVALID_CODE_PATH);
#endif
}
break;
case entitystate_battle:
switch (newState)
{
case entitystate_attack:
{
break;
}
case entitystate_idle:
case entitystate_dead:
resetEntityState(world, entity);
break;
default:
#ifdef DENGINE_DEBUG
ASSERT(INVALID_CODE_PATH);
#endif
}
break;
case entitystate_attack:
switch (newState)
{
case entitystate_battle:
setActiveEntityAnim(entity, animlist_hero_battlePose);
entity->stats->actionTimer = entity->stats->actionRate;
entity->stats->busyDuration = 0;
break;
// NOTE(doyle): Entity has been forced out of an attack (out of range)
case entitystate_idle:
case entitystate_dead:
resetEntityState(world, entity);
break;
default:
#ifdef DENGINE_DEBUG
ASSERT(INVALID_CODE_PATH);
#endif
}
break;
case entitystate_dead:
switch (newState)
{
case entitystate_idle:
case entitystate_battle:
case entitystate_attack:
default:
#ifdef DENGINE_DEBUG
ASSERT(INVALID_CODE_PATH);
#endif
}
break;
default:
#ifdef DENGINE_DEBUG
ASSERT(INVALID_CODE_PATH);
#endif
}
entity->state = newState;
}
INTERNAL void beginAttack(World *world, Entity *attacker) INTERNAL void beginAttack(World *world, Entity *attacker)
{ {
@ -782,8 +686,6 @@ INTERNAL void beginAttack(World *world, Entity *attacker)
ASSERT(attacker->stats->entityIdToAttack != ENTITY_NULL_ID); ASSERT(attacker->stats->entityIdToAttack != ENTITY_NULL_ID);
ASSERT(attacker->state == entitystate_battle); ASSERT(attacker->state == entitystate_battle);
#endif #endif
entityStateSwitch(world, attacker, entitystate_attack);
switch (attacker->stats->queuedAttack) switch (attacker->stats->queuedAttack)
{ {
case entityattack_tackle: case entityattack_tackle:
@ -806,8 +708,6 @@ INTERNAL void beginAttack(World *world, Entity *attacker)
} }
// TODO(doyle): Calculate the battle damage, transition back into battle pose ..
// etc
INTERNAL void endAttack(World *world, Entity *attacker) INTERNAL void endAttack(World *world, Entity *attacker)
{ {
#ifdef DENGINE_DEBUG #ifdef DENGINE_DEBUG
@ -868,36 +768,117 @@ INTERNAL void endAttack(World *world, Entity *attacker)
} }
} while (!defender && noMoreValidTargets == TRUE); } while (!defender && noMoreValidTargets == TRUE);
enum EntityState newAttackerState = entitystate_invalid;
if (!noMoreValidTargets) if (!noMoreValidTargets)
{ {
i32 damage = -10;
// TODO(doyle): Use attacker stats in battle equations // TODO(doyle): Use attacker stats in battle equations
if (attacker->type == entitytype_hero) if (attacker->type == entitytype_hero)
defender->stats->health -= 1; {
defender->stats->health += damage;
}
else else
{ {
// defender->stats->health--; // defender->stats->health--;
} }
}
}
if (defender->stats->health <= 0) INTERNAL void entityStateSwitch(World *world, Entity *entity,
enum EntityState newState)
{ {
#ifdef DENGINE_DEBUG #ifdef DENGINE_DEBUG
DEBUG_LOG("Entity has died"); ASSERT(world && entity)
ASSERT(entity->type == entitytype_mob || entity->type == entitytype_hero)
#endif #endif
entityStateSwitch(world, defender, entitystate_dead); if (entity->state == newState) return;
attacker->stats->entityIdToAttack =
findBestEntityToAttack(world, *attacker);
}
newAttackerState = entitystate_battle; switch(entity->state)
}
else
{ {
newAttackerState = entitystate_idle; case entitystate_idle:
switch (newState)
{
case entitystate_battle:
updateWorldBattleEntities(world, entity, ENTITY_IN_BATTLE);
entity->stats->entityIdToAttack =
findBestEntityToAttack(world, *entity);
break;
// TODO(doyle): Corner case- if move out of range and entity has
// switched to idle mode, we reach the attacker entity and they continue
// attacking it since there's no check before attack if entity is idle
// or not (i.e. has moved out of frame last frame).
case entitystate_dead:
setActiveEntityAnim(entity, animlist_hero_idle);
entity->stats->busyDuration = 0;
entity->stats->actionTimer = entity->stats->actionRate;
entity->stats->queuedAttack = entityattack_invalid;
entity->stats->entityIdToAttack = ENTITY_NULL_ID;
break;
case entitystate_attack:
default:
#ifdef DENGINE_DEBUG
ASSERT(INVALID_CODE_PATH);
#endif
}
break;
case entitystate_battle:
switch (newState)
{
case entitystate_attack:
{
beginAttack(world, entity);
break;
}
case entitystate_idle:
case entitystate_dead:
resetEntityState(world, entity);
break;
default:
#ifdef DENGINE_DEBUG
ASSERT(INVALID_CODE_PATH);
#endif
}
break;
case entitystate_attack:
switch (newState)
{
case entitystate_battle:
endAttack(world, entity);
setActiveEntityAnim(entity, animlist_hero_battlePose);
entity->stats->actionTimer = entity->stats->actionRate;
entity->stats->busyDuration = 0;
break;
// NOTE(doyle): Entity has been forced out of an attack (out of range)
case entitystate_idle:
case entitystate_dead:
resetEntityState(world, entity);
break;
default:
#ifdef DENGINE_DEBUG
ASSERT(INVALID_CODE_PATH);
#endif
}
break;
case entitystate_dead:
switch (newState)
{
case entitystate_idle:
case entitystate_battle:
case entitystate_attack:
default:
#ifdef DENGINE_DEBUG
ASSERT(INVALID_CODE_PATH);
#endif
}
break;
default:
#ifdef DENGINE_DEBUG
ASSERT(INVALID_CODE_PATH);
#endif
} }
/* Return attacker back to non-attacking state */ entity->state = newState;
entityStateSwitch(world, attacker, newAttackerState);
} }
@ -1003,7 +984,8 @@ void worldTraveller_gameUpdateAndRender(GameState *state, f32 dt)
if (stats->queuedAttack == entityattack_invalid) if (stats->queuedAttack == entityattack_invalid)
stats->queuedAttack = entityattack_tackle; stats->queuedAttack = entityattack_tackle;
beginAttack(world, entity); /* Launch up attack animation */
entityStateSwitch(world, entity, entitystate_attack);
} }
} }
else if (entity->state == entitystate_attack) else if (entity->state == entitystate_attack)
@ -1011,7 +993,37 @@ void worldTraveller_gameUpdateAndRender(GameState *state, f32 dt)
// TODO(doyle): Untested if the attacker and the defender same // TODO(doyle): Untested if the attacker and the defender same
Entity *attacker = entity; Entity *attacker = entity;
stats->busyDuration -= dt; stats->busyDuration -= dt;
if (stats->busyDuration <= 0) endAttack(world, attacker); if (stats->busyDuration <= 0)
{
/* Apply attack damage */
entityStateSwitch(world, entity, entitystate_battle);
/* Get target entity that was attacked */
Entity *defender = NULL;
for (i32 i = 0; i < world->maxEntities; i++)
{
i32 entityIdToAttack =
attacker->stats->entityIdToAttack;
if (world->entities[i].id == entityIdToAttack)
{
defender = &world->entities[i];
#ifdef DENGINE_DEBUG
ASSERT(defender->type == entitytype_mob ||
defender->type == entitytype_hero);
#endif
break;
}
}
if (defender->stats->health <= 0)
{
#ifdef DENGINE_DEBUG
DEBUG_LOG("Entity has died");
#endif
entityStateSwitch(world, defender, entitystate_dead);
entityStateSwitch(world, attacker, entitystate_idle);
}
}
} }
} }
@ -1092,20 +1104,10 @@ void worldTraveller_gameUpdateAndRender(GameState *state, f32 dt)
renderer_rect(&state->renderer, cameraBounds, heroCenter, renderer_rect(&state->renderer, cameraBounds, heroCenter,
V2(distance, 2.0f), V2(0, 0), angle, renderTex, V2(distance, 2.0f), V2(0, 0), angle, renderTex,
V4(1, 0, 0, 1.0f)); V4(1, 0, 0, 1.0f));
} }
} }
#ifdef DENGINE_DEBUG #ifdef DENGINE_DEBUG
renderer_rect(&state->renderer, cameraBounds, V2(500, 500), V2(100, 2.0f),
V2(0, 0), DEGREES_TO_RADIANS(0.0f), renderTex,
V4(0, 0, 1, 1.0f));
renderer_rect(&state->renderer, cameraBounds, V2(500, 500), V2(100, 2.0f),
V2(0, 0), DEGREES_TO_RADIANS(5.0f), renderTex,
V4(0, 0, 1, 1.0f));
renderer_rect(&state->renderer, cameraBounds, V2(500, 500), V2(100, 2.0f),
V2(0, 0), DEGREES_TO_RADIANS(90.0f), renderTex,
V4(0, 0, 1, 1.0f));
debug_drawUi(state, dt); debug_drawUi(state, dt);
#endif #endif
} }