Compare commits

..

2 Commits

Author SHA1 Message Date
4f4868efb0 Make transition functions more robust 2023-10-01 17:56:06 +11:00
00365ec86f fp: Add cooldowns to attacks 2023-10-01 17:56:04 +11:00
4 changed files with 86 additions and 37 deletions

View File

@ -386,7 +386,7 @@ void FP_EntityActionStateMachine(FP_Game *game, TELY_Audio *audio, TELY_Platform
switch (*state) {
case FP_EntityTerryState_Nil: {
action->next_state = FP_EntityTerryState_Idle;
FP_Game_EntityTransitionState(game, entity, FP_EntityTerryState_Idle);
} break;
case FP_EntityTerryState_Idle: {
@ -400,7 +400,7 @@ void FP_EntityActionStateMachine(FP_Game *game, TELY_Audio *audio, TELY_Platform
if (TELY_Platform_InputScanCodeIsPressed(input, TELY_PlatformInputScanCode_J) ||
TELY_Platform_InputGamepadButtonCodeIsPressed(input, 0, TELY_PlatformInputGamepadButtonCode_X)) {
} else if (dir_vector.x || dir_vector.y) {
action->next_state = FP_EntityTerryState_Run;
FP_Game_EntityTransitionState(game, entity, FP_EntityTerryState_Run);
}
}
} break;
@ -423,7 +423,7 @@ void FP_EntityActionStateMachine(FP_Game *game, TELY_Audio *audio, TELY_Platform
}
if (action_has_finished) {
action->next_state = FP_EntityTerryState_Idle;
FP_Game_EntityTransitionState(game, entity, FP_EntityTerryState_Idle);
}
} break;
@ -446,15 +446,15 @@ void FP_EntityActionStateMachine(FP_Game *game, TELY_Audio *audio, TELY_Platform
if (we_are_clicked_entity) {
if (TELY_Platform_InputScanCodeIsPressed(input, TELY_PlatformInputScanCode_J) ||
TELY_Platform_InputGamepadButtonCodeIsPressed(input, 0, TELY_PlatformInputGamepadButtonCode_X)) {
action->next_state = FP_EntityTerryState_Attack;
FP_Game_EntityTransitionState(game, entity, FP_EntityTerryState_Attack);
} else if (TELY_Platform_InputScanCodeIsPressed(input, TELY_PlatformInputScanCode_LeftShift) ||
TELY_Platform_InputGamepadButtonCodeIsPressed(input, 0, TELY_PlatformInputGamepadButtonCode_A)) {
action->next_state = FP_EntityTerryState_Dash;
FP_Game_EntityTransitionState(game, entity, FP_EntityTerryState_Dash);
}
}
if (!entity_has_velocity) {
action->next_state = FP_EntityTerryState_Idle;
FP_Game_EntityTransitionState(game, entity, FP_EntityTerryState_Idle);
}
} break;
@ -482,9 +482,9 @@ void FP_EntityActionStateMachine(FP_Game *game, TELY_Audio *audio, TELY_Platform
if (action_has_finished) {
if (entity_has_velocity) {
// TODO(doyle): Not sure if this branch triggers properly.
action->next_state = FP_EntityTerryState_Run;
FP_Game_EntityTransitionState(game, entity, FP_EntityTerryState_Run);
} else {
action->next_state = FP_EntityTerryState_Idle;
FP_Game_EntityTransitionState(game, entity, FP_EntityTerryState_Idle);
}
}
} break;
@ -540,7 +540,7 @@ void FP_EntityActionStateMachine(FP_Game *game, TELY_Audio *audio, TELY_Platform
FP_EntitySmoochieState *state = DQN_CAST(FP_EntitySmoochieState *) & action->state;
switch (*state) {
case FP_EntitySmoochieState_Nil: {
action->next_state = FP_EntitySmoochieState_Idle;
FP_Game_EntityTransitionState(game, entity, FP_EntitySmoochieState_Idle);
} break;
case FP_EntitySmoochieState_Idle: {
@ -554,14 +554,14 @@ void FP_EntityActionStateMachine(FP_Game *game, TELY_Audio *audio, TELY_Platform
if (we_are_clicked_entity) {
if (TELY_Platform_InputScanCodeIsPressed(input, TELY_PlatformInputScanCode_J) ||
TELY_Platform_InputGamepadButtonCodeIsPressed(input, 0, TELY_PlatformInputGamepadButtonCode_X)) {
action->next_state = FP_EntitySmoochieState_Attack;
FP_Game_EntityTransitionState(game, entity, FP_EntitySmoochieState_Attack);
} else if (dir_vector.x || dir_vector.y) {
action->next_state = FP_EntitySmoochieState_Run;
FP_Game_EntityTransitionState(game, entity, FP_EntitySmoochieState_Run);
}
}
if (entity_has_velocity) {
action->next_state = FP_EntitySmoochieState_Run;
FP_Game_EntityTransitionState(game, entity, FP_EntitySmoochieState_Run);
}
} break;
@ -612,7 +612,7 @@ void FP_EntityActionStateMachine(FP_Game *game, TELY_Audio *audio, TELY_Platform
}
// NOTE: Transition out of the action
action->next_state = FP_EntitySmoochieState_Idle;
FP_Game_EntityTransitionState(game, entity, FP_EntitySmoochieState_Idle);
}
} break;
@ -625,7 +625,7 @@ void FP_EntityActionStateMachine(FP_Game *game, TELY_Audio *audio, TELY_Platform
}
if (action_has_finished)
action->next_state = FP_EntitySmoochieState_Idle;
FP_Game_EntityTransitionState(game, entity, FP_EntitySmoochieState_Idle);
} break;
case FP_EntitySmoochieState_AttackHeart: {
@ -663,18 +663,18 @@ void FP_EntityActionStateMachine(FP_Game *game, TELY_Audio *audio, TELY_Platform
if (we_are_clicked_entity) {
if (TELY_Platform_InputScanCodeIsPressed(input, TELY_PlatformInputScanCode_J) ||
TELY_Platform_InputGamepadButtonCodeIsPressed(input, 0, TELY_PlatformInputGamepadButtonCode_X)) {
action->next_state = FP_EntitySmoochieState_Attack;
FP_Game_EntityTransitionState(game, entity, FP_EntitySmoochieState_Attack);
}
}
if (!entity_has_velocity) {
action->next_state = FP_EntitySmoochieState_Idle;
FP_Game_EntityTransitionState(game, entity, FP_EntitySmoochieState_Idle);
}
} break;
}
if (entity->is_dying && *state != FP_EntitySmoochieState_Death) {
action->next_state = FP_EntitySmoochieState_Death;
FP_Game_EntityTransitionState(game, entity, FP_EntitySmoochieState_Death);
}
if (*state == FP_EntitySmoochieState_Attack) {
@ -712,7 +712,7 @@ void FP_EntityActionStateMachine(FP_Game *game, TELY_Audio *audio, TELY_Platform
FP_EntityClingerState *state = DQN_CAST(FP_EntityClingerState *)&action->state;
switch (*state) {
case FP_EntityClingerState_Nil: {
action->next_state = FP_EntityClingerState_Idle;
FP_Game_EntityTransitionState(game, entity, FP_EntityClingerState_Idle);
} break;
case FP_EntityClingerState_Idle: {
@ -725,14 +725,14 @@ void FP_EntityActionStateMachine(FP_Game *game, TELY_Audio *audio, TELY_Platform
if (we_are_clicked_entity) {
if (TELY_Platform_InputScanCodeIsPressed(input, TELY_PlatformInputScanCode_J) ||
TELY_Platform_InputGamepadButtonCodeIsPressed(input, 0, TELY_PlatformInputGamepadButtonCode_X)) {
action->next_state = FP_EntityClingerState_Attack;
FP_Game_EntityTransitionState(game, entity, FP_EntityClingerState_Attack);
} else if (dir_vector.x || dir_vector.y) {
action->next_state = FP_EntityClingerState_Run;
FP_Game_EntityTransitionState(game, entity, FP_EntityClingerState_Run);
}
}
if (entity_has_velocity) {
action->next_state = FP_EntityClingerState_Run;
FP_Game_EntityTransitionState(game, entity, FP_EntityClingerState_Run);
}
} break;
@ -755,7 +755,7 @@ void FP_EntityActionStateMachine(FP_Game *game, TELY_Audio *audio, TELY_Platform
}
if (action_has_finished)
action->next_state = FP_EntityClingerState_Idle;
FP_Game_EntityTransitionState(game, entity, FP_EntityClingerState_Idle);
} break;
case FP_EntityClingerState_Death: {
@ -790,18 +790,18 @@ void FP_EntityActionStateMachine(FP_Game *game, TELY_Audio *audio, TELY_Platform
if (we_are_clicked_entity) {
if (TELY_Platform_InputScanCodeIsPressed(input, TELY_PlatformInputScanCode_J) ||
TELY_Platform_InputGamepadButtonCodeIsPressed(input, 0, TELY_PlatformInputGamepadButtonCode_X)) {
action->next_state = FP_EntityClingerState_Attack;
FP_Game_EntityTransitionState(game, entity, FP_EntityClingerState_Attack);
}
}
if (!entity_has_velocity) {
action->next_state = FP_EntityClingerState_Idle;
FP_Game_EntityTransitionState(game, entity, FP_EntityClingerState_Idle);
}
} break;
}
if (entity->is_dying && *state != FP_EntityClingerState_Death) {
action->next_state = FP_EntityClingerState_Death;
FP_Game_EntityTransitionState(game, entity, FP_EntityClingerState_Death);
}
if (*state == FP_EntityClingerState_Attack) { // NOTE: Position the attack box
@ -838,7 +838,7 @@ void FP_EntityActionStateMachine(FP_Game *game, TELY_Audio *audio, TELY_Platform
FP_EntityMerchantTerryState *state = DQN_CAST(FP_EntityMerchantTerryState *)&action->state;
switch (*state) {
case FP_EntityMerchantTerryState_Nil: {
action->next_state = FP_EntityMerchantTerryState_Idle;
FP_Game_EntityTransitionState(game, entity, FP_EntityMerchantTerryState_Idle);
} break;
case FP_EntityMerchantTerryState_Idle: {
@ -856,7 +856,7 @@ void FP_EntityActionStateMachine(FP_Game *game, TELY_Audio *audio, TELY_Platform
FP_EntityMerchantPhoneCompanyState *state = DQN_CAST(FP_EntityMerchantPhoneCompanyState *)&action->state;
switch (*state) {
case FP_EntityMerchantPhoneCompanyState_Nil: {
action->next_state = FP_EntityMerchantPhoneCompanyState_Idle;
FP_Game_EntityTransitionState(game, entity, FP_EntityMerchantPhoneCompanyState_Idle);
} break;
case FP_EntityMerchantPhoneCompanyState_Idle: {
@ -873,7 +873,7 @@ void FP_EntityActionStateMachine(FP_Game *game, TELY_Audio *audio, TELY_Platform
FP_EntityMerchantGymState *state = DQN_CAST(FP_EntityMerchantGymState *)&action->state;
switch (*state) {
case FP_EntityMerchantGymState_Nil: {
action->next_state = FP_EntityMerchantGymState_Idle;
FP_Game_EntityTransitionState(game, entity, FP_EntityMerchantGymState_Idle);
} break;
case FP_EntityMerchantGymState_Idle: {
@ -891,7 +891,7 @@ void FP_EntityActionStateMachine(FP_Game *game, TELY_Audio *audio, TELY_Platform
FP_EntityMerchantGraveyardState *state = DQN_CAST(FP_EntityMerchantGraveyardState *)&action->state;
switch (*state) {
case FP_EntityMerchantGraveyardState_Nil: {
action->next_state = FP_EntityMerchantGraveyardState_Idle;
FP_Game_EntityTransitionState(game, entity, FP_EntityMerchantGraveyardState_Idle);
} break;
case FP_EntityMerchantGraveyardState_Idle: {
@ -909,7 +909,7 @@ void FP_EntityActionStateMachine(FP_Game *game, TELY_Audio *audio, TELY_Platform
FP_EntityClubTerryState *state = DQN_CAST(FP_EntityClubTerryState *)&action->state;
switch (*state) {
case FP_EntityClubTerryState_Nil: {
action->next_state = FP_EntityClubTerryState_Idle;
FP_Game_EntityTransitionState(game, entity, FP_EntityClubTerryState_Idle);
} break;
case FP_EntityClubTerryState_Idle: {
@ -935,7 +935,7 @@ void FP_EntityActionStateMachine(FP_Game *game, TELY_Audio *audio, TELY_Platform
patron->base_acceleration_per_s.meters *= .5f;
entity->club_terry_patron = {};
}
action->next_state = FP_EntityClubTerryState_Idle;
FP_Game_EntityTransitionState(game, entity, FP_EntityClubTerryState_Idle);
}
} break;
}
@ -946,7 +946,7 @@ void FP_EntityActionStateMachine(FP_Game *game, TELY_Audio *audio, TELY_Platform
FP_EntityMapState *state = DQN_CAST(FP_EntityMapState *) & action->state;
switch (*state) {
case FP_EntityMapState_Nil: {
action->next_state = FP_EntityMapState_Idle;
FP_Game_EntityTransitionState(game, entity, FP_EntityMapState_Idle);
} break;
case FP_EntityMapState_Idle: {
@ -964,7 +964,7 @@ void FP_EntityActionStateMachine(FP_Game *game, TELY_Audio *audio, TELY_Platform
FP_EntityHeartState *state = DQN_CAST(FP_EntityHeartState *) & action->state;
switch (*state) {
case FP_EntityHeartState_Nil: {
action->next_state = FP_EntityHeartState_Idle;
FP_Game_EntityTransitionState(game, entity, FP_EntityHeartState_Idle);
} break;
case FP_EntityHeartState_Idle: {
@ -1257,7 +1257,7 @@ void FP_Update(TELY_Platform *platform, FP_Game *game, TELY_PlatformInput *input
FP_GameEntity *club = waypoint_entity;
if (FP_Game_IsNilEntityHandle(game, club->club_terry_patron)) {
club->club_terry_patron = entity->handle;
club->action.next_state = FP_EntityClubTerryState_PartyTime;
FP_Game_EntityTransitionState(game, club, FP_EntityClubTerryState_PartyTime);
entity->flags |= FP_GameEntityFlag_PartyingAtClubTerry;
Dqn_Rect hit_box = FP_Game_CalcEntityWorldHitBox(game, club->handle);
@ -1297,12 +1297,12 @@ void FP_Update(TELY_Platform *platform, FP_Game *game, TELY_PlatformInput *input
// TODO(doyle): We should check if it's valid to enter this new state
// from the entity's current state
if (entity->type == FP_EntityType_Terry) {
entity->action.next_state = FP_EntityTerryState_Attack;
FP_Game_EntityTransitionState(game, entity, FP_EntityTerryState_Attack);
} else if (entity->type == FP_EntityType_Smoochie) {
entity->action.next_state = FP_EntitySmoochieState_Attack;
FP_Game_EntityTransitionState(game, entity, FP_EntitySmoochieState_Attack);
} else {
DQN_ASSERT(entity->type == FP_EntityType_Clinger);
entity->action.next_state = FP_EntityClingerState_Attack;
FP_Game_EntityTransitionState(game, entity, FP_EntityClingerState_Attack);
}
entity->direction = best_attack_dir;

View File

@ -56,6 +56,7 @@ static FP_GameEntityHandle FP_Entity_CreateClinger(FP_Game *game, Dqn_V2 pos, DQ
entity->base_acceleration_per_s.meters = 8.f;
entity->local_pos = pos;
entity->sprite_height.meters = 1.6f;
entity->attack_cooldown_ms = 1000;
entity->local_hit_box_size = FP_Game_MetersToPixelsNx2(game, 0.4f, entity->sprite_height.meters * .5f);
FP_Entity_AddDebugEditorFlags(game, entity->handle);
@ -78,6 +79,7 @@ static FP_GameEntityHandle FP_Entity_CreateSmoochie(FP_Game *game, Dqn_V2 pos, D
entity->is_dying = false;
entity->local_pos = pos;
entity->sprite_height.meters = 1.6f;
entity->attack_cooldown_ms = 1000;
entity->local_hit_box_size = FP_Game_MetersToPixelsNx2(game, 0.4f, entity->sprite_height.meters * .6f);
FP_Entity_AddDebugEditorFlags(game, entity->handle);
entity->flags |= FP_GameEntityFlag_NonTraversable;
@ -142,6 +144,7 @@ static FP_GameEntityHandle FP_Entity_CreateTerry(FP_Game *game, Dqn_V2 pos, DQN_
entity->local_pos = pos;
entity->base_acceleration_per_s.meters = 16.f;
entity->sprite_height.meters = 1.8f;
entity->attack_cooldown_ms = 500;
entity->local_hit_box_size = FP_Game_MetersToPixelsNx2(game, 0.5f, entity->sprite_height.meters * .6f);
FP_Entity_AddDebugEditorFlags(game, result);
entity->flags |= FP_GameEntityFlag_NonTraversable;

View File

@ -723,3 +723,47 @@ FP_GameFindClosestEntityResult FP_Game_FindClosestEntityWithType(FP_Game *game,
return result;
}
static bool FP_Game_CanEntityAttack(FP_GameEntity *entity, uint64_t current_time_ms)
{
bool result = (current_time_ms - entity->last_attack_timestamp) >= entity->attack_cooldown_ms;
return result;
}
static void FP_Game_EntityTransitionState(FP_Game *game, FP_GameEntity *entity, uint32_t desired_state)
{
switch (entity->type) {
case FP_EntityType_Terry: {
if (desired_state == FP_EntityTerryState_Attack) {
if (!FP_Game_CanEntityAttack(entity, game->clock_ms)) {
// NOTE: Cooldown not met do not transition
return;
}
entity->last_attack_timestamp = game->clock_ms;
}
} break;
case FP_EntityType_Smoochie: {
if (desired_state == FP_EntitySmoochieState_Attack) {
if (!FP_Game_CanEntityAttack(entity, game->clock_ms)) {
// NOTE: Cooldown not met do not transition
return;
}
entity->last_attack_timestamp = game->clock_ms;
}
} break;
case FP_EntityType_Clinger: {
if (desired_state == FP_EntityClingerState_Attack) {
if (!FP_Game_CanEntityAttack(entity, game->clock_ms)) {
// NOTE: Cooldown not met do not transition
return;
}
entity->last_attack_timestamp = game->clock_ms;
}
} break;
}
// NOTE: If no returns are hit above we proceed with the state change
entity->action.next_state = desired_state;
}

View File

@ -169,6 +169,8 @@ struct FP_GameEntity
Dqn_V2 attack_box_offset;
bool attack_processed;
bool is_dying;
uint64_t last_attack_timestamp;
uint64_t attack_cooldown_ms;
Dqn_FArray<Dqn_V2, 8> spawner_waypoints;
FP_SentinelList<FP_GameEntityHandle> spawn_list;