diff --git a/feely_pona.cpp b/feely_pona.cpp index 12edf99..4554466 100644 --- a/feely_pona.cpp +++ b/feely_pona.cpp @@ -1001,13 +1001,12 @@ void FP_EntityActionStateMachine(FP_Game *game, TELY_Audio *audio, TELY_Platform } if (action_has_finished) { - if (!FP_Game_IsNilEntityHandle(game, entity->club_terry_patron)) { - FP_GameEntity *patron = FP_Game_GetEntity(game, entity->club_terry_patron); - patron->flags |= FP_GameEntityFlag_ExperiencedClubTerry; - patron->flags &= ~FP_GameEntityFlag_PartyingAtClubTerry; + if (!FP_Game_IsNilEntityHandle(game, entity->building_patron)) { + FP_GameEntity *patron = FP_Game_GetEntity(game, entity->building_patron); + patron->flags &= ~FP_GameEntityFlag_OccupiedInBuilding; patron->base_acceleration_per_s.meters *= .5f; - entity->club_terry_patron = {}; } + entity->building_patron = {}; FP_Game_EntityTransitionState(game, entity, FP_EntityClubTerryState_Idle); } } break; @@ -1063,6 +1062,19 @@ void FP_EntityActionStateMachine(FP_Game *game, TELY_Audio *audio, TELY_Platform } break; case FP_EntityAirportTerryState_FlyPassenger: { + if (entering_new_state) { + uint64_t duration_ms = 5000; + FP_Game_EntityActionReset(game, entity->handle, duration_ms, render_data.sprite); + } + + if (action_has_finished) { + if (!FP_Game_IsNilEntityHandle(game, entity->building_patron)) { + FP_GameEntity *patron = FP_Game_GetEntity(game, entity->building_patron); + patron->flags &= ~FP_GameEntityFlag_OccupiedInBuilding; + } + entity->building_patron = {}; + FP_Game_EntityTransitionState(game, entity, FP_EntityAirportTerryState_Idle); + } } break; } } break; @@ -1182,7 +1194,22 @@ void FP_EntityActionStateMachine(FP_Game *game, TELY_Audio *audio, TELY_Platform FP_Game_EntityActionReset(game, entity->handle, duration_ms, render_data.sprite); } } break; - case FP_EntityChurchTerryState_ConvertPatron: break; + + case FP_EntityChurchTerryState_ConvertPatron: { + if (entering_new_state) { + uint64_t duration_ms = 5000; + FP_Game_EntityActionReset(game, entity->handle, duration_ms, render_data.sprite); + } + + if (action_has_finished) { + if (!FP_Game_IsNilEntityHandle(game, entity->building_patron)) { + FP_GameEntity *patron = FP_Game_GetEntity(game, entity->building_patron); + patron->flags &= ~FP_GameEntityFlag_OccupiedInBuilding; + } + entity->building_patron = {}; + FP_Game_EntityTransitionState(game, entity, FP_EntityChurchTerryState_Idle); + } + } break; } } break; @@ -1286,7 +1313,7 @@ void FP_Update(TELY_Platform *platform, FP_Game *game, TELY_PlatformInput *input FP_GameEntity *entity = it.entity; entity->alive_time_s += PHYSICS_STEP; - if (entity->flags & FP_GameEntityFlag_PartyingAtClubTerry) + if (entity->flags & FP_GameEntityFlag_OccupiedInBuilding) continue; // NOTE: Move entity by keyboard and gamepad =============================================== @@ -1344,7 +1371,6 @@ void FP_Update(TELY_Platform *platform, FP_Game *game, TELY_PlatformInput *input Dqn_V2 entity_pos = FP_Game_CalcEntityWorldPos(game, entity->handle); if (entity->flags & FP_GameEntityFlag_AggrosWhenNearTerry) { - FP_GameFindClosestEntityResult closest_result = FP_Game_FindClosestEntityWithType(game, entity->handle, FP_EntityType_Terry); Dqn_f32 aggro_dist_threshold = FP_Game_MetersToPixelsNx1(game, 4.f); @@ -1391,31 +1417,62 @@ void FP_Update(TELY_Platform *platform, FP_Game *game, TELY_PlatformInput *input } } - if (entity->flags & FP_GameEntityFlag_RespondsToClubTerry && (entity->flags & FP_GameEntityFlag_ExperiencedClubTerry) == 0) { - FP_GameFindClosestEntityResult closest_club = FP_Game_FindClosestEntityWithType(game, entity->handle, FP_EntityType_ClubTerry); - if (closest_club.dist_squared < DQN_SQUARED(FP_Game_MetersToPixelsNx1(game, 4.f))) { + if (entity->flags & FP_GameEntityFlag_RespondsToBuildings) { + FP_GameFindClosestEntityResult closest_building = {}; + closest_building.dist_squared = DQN_F32_MAX; + closest_building.pos = Dqn_V2_InitNx1(DQN_F32_MAX); - bool has_waypoint_to_club = false; - for (FP_SentinelListLink *link = nullptr; - !has_waypoint_to_club && FP_SentinelList_Iterate(&entity->waypoints, &link); ) { - has_waypoint_to_club = link->data.entity == closest_club.entity; + for (FP_GameEntityIterator building_it = {}; FP_Game_DFSPostOrderWalkEntityTree(game, &building_it, game->root_entity); ) { + FP_GameEntity *it_entity = building_it.entity; + if (it_entity->type != FP_EntityType_ClubTerry && + it_entity->type != FP_EntityType_AirportTerry && + it_entity->type != FP_EntityType_ChurchTerry) + continue; + + bool already_visited_building = false; + for (FP_SentinelListLink *link_it = {}; + !already_visited_building && FP_SentinelList_Iterate(&entity->buildings_visited, &link_it); + ) { + FP_GameEntityHandle visit_item = link_it->data; + already_visited_building = visit_item == it_entity->handle; } - if (!has_waypoint_to_club) { - Dqn_Rect club_hit_box = FP_Game_CalcEntityWorldHitBox(game, closest_club.entity); - Dqn_V2 club_top_left = Dqn_Rect_TopLeft(club_hit_box); - Dqn_V2 club_top_right = Dqn_Rect_TopRight(club_hit_box); + if (already_visited_building) + continue; + + Dqn_V2 pos = FP_Game_CalcEntityWorldPos(game, it_entity->handle); + Dqn_f32 dist = Dqn_V2_LengthSq_V2x2(pos, entity_pos); + if (dist < closest_building.dist_squared) { + closest_building.pos = pos; + closest_building.dist_squared = dist; + closest_building.entity = it_entity->handle; + } + } + + if (!FP_Game_IsNilEntityHandle(game, closest_building.entity) && + closest_building.dist_squared < DQN_SQUARED(FP_Game_MetersToPixelsNx1(game, 5.f))) { + + bool has_waypoint_to_building = false; + for (FP_SentinelListLink *link = nullptr; + !has_waypoint_to_building && FP_SentinelList_Iterate(&entity->waypoints, &link); ) { + has_waypoint_to_building = link->data.entity == closest_building.entity; + } + + if (!has_waypoint_to_building) { + Dqn_Rect hit_box = FP_Game_CalcEntityWorldHitBox(game, closest_building.entity); + Dqn_V2 top_left = Dqn_Rect_TopLeft(hit_box); + Dqn_V2 top_right = Dqn_Rect_TopRight(hit_box); FP_SentinelListLink *link = FP_SentinelList_MakeBefore(&entity->waypoints, FP_SentinelList_Front(&entity->waypoints), game->chunk_pool); FP_GameWaypoint *waypoint = &link->data; - waypoint->entity = closest_club.entity; + waypoint->entity = closest_building.entity; waypoint->type = FP_GameWaypointType_Side; - if (entity_pos.x <= club_top_left.x) { + if (entity_pos.x <= top_left.x) { waypoint->type_direction = FP_GameDirection_Left; - } else if (entity_pos.x >= club_top_right.x) { + } else if (entity_pos.x >= top_right.x) { waypoint->type_direction = FP_GameDirection_Right; - } else if (entity_pos.y <= club_top_left.y) { + } else if (entity_pos.y <= top_left.y) { waypoint->type_direction = FP_GameDirection_Up; } else { waypoint->type_direction = FP_GameDirection_Down; @@ -1492,29 +1549,37 @@ void FP_Update(TELY_Platform *platform, FP_Game *game, TELY_PlatformInput *input } // NOTE: We have arrived at the waypoint - bool aggro_on_terry = (entity->flags & FP_GameEntityFlag_AggrosWhenNearTerry) && waypoint_entity->type == FP_EntityType_Terry; + bool aggro_on_terry = (entity->flags & FP_GameEntityFlag_AggrosWhenNearTerry) && waypoint_entity->type == FP_EntityType_Terry; + bool building_response = (entity->flags & FP_GameEntityFlag_RespondsToBuildings) && + waypoint_entity->type == FP_EntityType_ClubTerry || + waypoint_entity->type == FP_EntityType_AirportTerry || + waypoint_entity->type == FP_EntityType_ChurchTerry; + bool heart_response = (entity->flags & FP_GameEntityFlag_PointOfInterestHeart) && waypoint_entity->type == FP_EntityType_Heart; - bool club_terry_response = (entity->flags & FP_GameEntityFlag_RespondsToClubTerry) && - ((entity->flags & FP_GameEntityFlag_ExperiencedClubTerry) == 0) && - waypoint_entity->type == FP_EntityType_ClubTerry; - - bool heart_response = (entity->flags & FP_GameEntityFlag_PointOfInterestHeart) && waypoint_entity->type == FP_EntityType_Heart; - - if (((waypoint->flags & FP_GameWaypointFlag_NonInterruptible) == 0) && (aggro_on_terry || club_terry_response || heart_response)) { + if (((waypoint->flags & FP_GameWaypointFlag_NonInterruptible) == 0) && (aggro_on_terry || building_response || heart_response)) { bool can_attack = !entity->is_dying; // TODO(doyle): State transition needs to check if it's valid to move to making this not necessary - if (club_terry_response) { - FP_GameEntity *club = waypoint_entity; - if (FP_Game_IsNilEntityHandle(game, club->club_terry_patron)) { - club->club_terry_patron = entity->handle; - FP_Game_EntityTransitionState(game, club, FP_EntityClubTerryState_PartyTime); - entity->flags |= FP_GameEntityFlag_PartyingAtClubTerry; + if (building_response) { + FP_GameEntity *building = waypoint_entity; + if (FP_Game_IsNilEntityHandle(game, building->building_patron)) { + building->building_patron = entity->handle; - Dqn_Rect hit_box = FP_Game_CalcEntityWorldHitBox(game, club->handle); + if (building->type == FP_EntityType_ClubTerry) { + FP_Game_EntityTransitionState(game, building, FP_EntityClubTerryState_PartyTime); + } else if (building->type == FP_EntityType_AirportTerry) { + FP_Game_EntityTransitionState(game, building, FP_EntityAirportTerryState_FlyPassenger); + } else { + DQN_ASSERT(building->type == FP_EntityType_ChurchTerry); + FP_Game_EntityTransitionState(game, building, FP_EntityChurchTerryState_ConvertPatron); + } + + entity->flags |= FP_GameEntityFlag_OccupiedInBuilding; + Dqn_Rect hit_box = FP_Game_CalcEntityWorldHitBox(game, building->handle); Dqn_V2 exit_pos = Dqn_Rect_InterpolatedPoint(hit_box, Dqn_V2_InitNx2(0.5f, 1.1f)); entity->local_pos = exit_pos; // TODO(doyle): Only works when parent world pos is 0,0 can_attack = false; FP_SentinelList_Erase(&entity->waypoints, waypoint_link, game->chunk_pool); + FP_SentinelList_Add(&entity->buildings_visited, game->chunk_pool, building->handle); } } @@ -1557,23 +1622,22 @@ void FP_Update(TELY_Platform *platform, FP_Game *game, TELY_PlatformInput *input DQN_ASSERT(entity->type == FP_EntityType_Clinger); FP_Game_EntityTransitionState(game, entity, FP_EntityClingerState_Attack); } - entity->direction = best_attack_dir; } break; - case FP_EntityType_Count: DQN_INVALID_CODE_PATH; break; - - case FP_EntityType_Nil: break; - case FP_EntityType_ClubTerry: break; - case FP_EntityType_Map: break; - case FP_EntityType_Heart: break; - case FP_EntityType_MerchantTerry: break; - case FP_EntityType_MerchantGraveyard: break; - case FP_EntityType_MerchantGym: break; - case FP_EntityType_MerchantPhoneCompany: break; - case FP_EntityType_AirportTerry: - case FP_EntityType_ChurchTerry: - case FP_EntityType_KennelTerry: break; + case FP_EntityType_Nil: break; + case FP_EntityType_ClubTerry: break; + case FP_EntityType_Map: break; + case FP_EntityType_Heart: break; + case FP_EntityType_MerchantTerry: break; + case FP_EntityType_MerchantGraveyard: break; + case FP_EntityType_MerchantGym: break; + case FP_EntityType_MerchantPhoneCompany: break; + case FP_EntityType_AirportTerry: break; + case FP_EntityType_ChurchTerry: break; + case FP_EntityType_KennelTerry: break; + case FP_EntityType_PhoneMessageProjectile: break; + case FP_EntityType_Count: DQN_INVALID_CODE_PATH; break; } } @@ -1731,7 +1795,7 @@ void FP_Update(TELY_Platform *platform, FP_Game *game, TELY_PlatformInput *input FP_GameEntity *mob = FP_Game_GetEntity(game, link->data); mob->waypoints = FP_SentinelList_Init(game->chunk_pool); mob->flags |= FP_GameEntityFlag_AggrosWhenNearTerry; - mob->flags |= FP_GameEntityFlag_RespondsToClubTerry; + mob->flags |= FP_GameEntityFlag_RespondsToBuildings; for (FP_GameEntity *waypoint_entity = entity->first_child; waypoint_entity; waypoint_entity = waypoint_entity->next) { if ((waypoint_entity->flags & FP_GameEntityFlag_MobSpawnerWaypoint) == 0) @@ -1879,8 +1943,7 @@ void FP_Render(FP_Game *game, TELY_Platform *platform, TELY_Renderer *renderer) // NOTE: Draw entities ========================================================================= for (FP_GameEntityIterator it = {}; FP_Game_DFSPostOrderWalkEntityTree(game, &it, game->root_entity); ) { FP_GameEntity *entity = it.entity; - - if (entity->flags & FP_GameEntityFlag_PartyingAtClubTerry) + if (entity->flags & FP_GameEntityFlag_OccupiedInBuilding) continue; // NOTE: Render shapes in entity =========================================================== @@ -2009,12 +2072,17 @@ void FP_Render(FP_Game *game, TELY_Platform *platform, TELY_Renderer *renderer) TELY_COLOUR_WHITE_V4); } - if (entity->type == FP_EntityType_ClubTerry) { - FP_EntityClubTerryState *state = DQN_CAST(FP_EntityClubTerryState *)&entity->action.state; - if (*state == FP_EntityClubTerryState_PartyTime) { - Dqn_f32 duration = entity->action.end_at_clock_ms - DQN_CAST(Dqn_f32)entity->action.started_at_clock_ms; - Dqn_f32 elapsed = DQN_CAST(Dqn_f32)(game->clock_ms - entity->action.started_at_clock_ms); + if (entity->type == FP_EntityType_ClubTerry || + entity->type == FP_EntityType_AirportTerry || + entity->type == FP_EntityType_ChurchTerry) { + FP_GameEntityAction const *action = &entity->action; + bool draw_timer = entity->type == FP_EntityType_ClubTerry && action->state == FP_EntityClubTerryState_PartyTime || + entity->type == FP_EntityType_AirportTerry && action->state == FP_EntityAirportTerryState_FlyPassenger || + entity->type == FP_EntityType_ChurchTerry && action->state == FP_EntityChurchTerryState_ConvertPatron; + if (draw_timer) { + Dqn_f32 duration = action->end_at_clock_ms - DQN_CAST(Dqn_f32)action->started_at_clock_ms; + Dqn_f32 elapsed = DQN_CAST(Dqn_f32)(game->clock_ms - action->started_at_clock_ms); Dqn_f32 t01 = DQN_MIN(1.f, elapsed / duration); Dqn_Rect rect = {}; rect.pos = Dqn_Rect_InterpolatedPoint(world_hit_box, Dqn_V2_InitNx2(0, -0.3f)); diff --git a/feely_pona_game.cpp b/feely_pona_game.cpp index f363da6..9ccae21 100644 --- a/feely_pona_game.cpp +++ b/feely_pona_game.cpp @@ -222,6 +222,7 @@ static FP_GameEntity *FP_Game_MakeEntityPointerFV(FP_Game *game, DQN_FMT_STRING_ result->sprite_height.meters = 1; result->parent = FP_Game_ActiveParentEntityPointer(game); result->name = TELY_ChunkPool_AllocFmtFV(game->chunk_pool, fmt, args); + result->buildings_visited = FP_SentinelList_Init(game->chunk_pool); // NOTE: Attach entity as a child to the parent FP_GameEntity *parent = result->parent; @@ -317,6 +318,7 @@ static void FP_Game_DetachEntityIntoFreeList(FP_Game *game, FP_GameEntityHandle FP_SentinelList_Deinit(&entity->spawn_list, game->chunk_pool); FP_SentinelList_Deinit(&entity->waypoints, game->chunk_pool); + FP_SentinelList_Deinit(&entity->buildings_visited, game->chunk_pool); if (new_entity_generation > entity_generation) { // NOTE: Update the incremented handle disassociating all prior handles @@ -763,6 +765,27 @@ static void FP_Game_EntityTransitionState(FP_Game *game, FP_GameEntity *entity, entity->last_attack_timestamp = game->clock_ms; } } break; + + case FP_EntityType_AirportTerry: { + } break; + + case FP_EntityType_ChurchTerry: { + } break; + + case FP_EntityType_ClubTerry: { + } break; + + case FP_EntityType_Nil: + case FP_EntityType_Catfish: + case FP_EntityType_Heart: + case FP_EntityType_KennelTerry: + case FP_EntityType_Map: + case FP_EntityType_MerchantGraveyard: + case FP_EntityType_MerchantGym: + case FP_EntityType_MerchantPhoneCompany: + case FP_EntityType_MerchantTerry: + case FP_EntityType_PhoneMessageProjectile: + case FP_EntityType_Count: break; } // NOTE: If no returns are hit above we proceed with the state change entity->action.next_state = desired_state; diff --git a/feely_pona_game.h b/feely_pona_game.h index 5f8d9e3..e02f359 100644 --- a/feely_pona_game.h +++ b/feely_pona_game.h @@ -16,10 +16,9 @@ enum FP_GameEntityFlag FP_GameEntityFlag_MobSpawnerWaypoint = 1 << 8, FP_GameEntityFlag_AggrosWhenNearTerry = 1 << 9, FP_GameEntityFlag_Attackable = 1 << 10, - FP_GameEntityFlag_RespondsToClubTerry = 1 << 11, - FP_GameEntityFlag_PartyingAtClubTerry = 1 << 12, - FP_GameEntityFlag_ExperiencedClubTerry = 1 << 13, - FP_GameEntityFlag_PointOfInterestHeart = 1 << 11, + FP_GameEntityFlag_RespondsToBuildings = 1 << 11, + FP_GameEntityFlag_OccupiedInBuilding = 1 << 12, + FP_GameEntityFlag_PointOfInterestHeart = 1 << 13, FP_GameEntityFlag_CameraTracking = 1 << 14, FP_GameEntityFlag_BuildZone = 1 << 15, FP_GameEntityFlag_TTL = 1 << 16, @@ -162,7 +161,7 @@ struct FP_GameEntity FP_SentinelList waypoints; FP_GameEntityHandle aggro_slot[FP_GameDirection_Count]; - FP_GameEntityHandle club_terry_patron; + FP_GameEntityHandle building_patron; // NOTE: The entity hit box is positioned at the center of the entity. Dqn_V2 local_hit_box_size; @@ -193,6 +192,7 @@ struct FP_GameEntity Dqn_FArray shapes; Dqn_FArray projectiles; uint64_t ttl_end_timestamp; + FP_SentinelList buildings_visited; }; struct FP_GameEntityIterator