fp: Improve swarming of buildings

This commit is contained in:
2023-10-07 19:14:09 +11:00
parent 11cab9a726
commit d564ad225d
4 changed files with 159 additions and 111 deletions
+104 -88
View File
@@ -177,11 +177,15 @@ static void FP_Game_MoveEntity(FP_Game *game, FP_GameEntityHandle entity_handle,
if (collider->type == FP_EntityType_Smoochie || collider->type == FP_EntityType_Clinger || collider->type == FP_EntityType_Catfish) {
entity_collides_with_collider = false;
} else if (FP_Entity_IsBuildingForMobs(collider)) {
#if 0
// NOTE: We disable collision on buildings we have visited to avoid some
// problems ...
if (FP_SentinelList_Find(&entity->buildings_visited, collider->handle)) {
entity_collides_with_collider = false;
}
#else
entity_collides_with_collider = false;
#endif
}
} break;
@@ -439,7 +443,10 @@ void TELY_DLL_Init(void *user_data)
game->merchant_phone_company = FP_Entity_CreateMerchantPhoneCompany(game, base_top_right, "PhoneCompany");
}
FP_Entity_CreateClubTerry(game, Dqn_V2_InitNx2(567, -191), "Club Terry");
FP_Entity_CreateClubTerry(game, Dqn_V2_InitNx2(+500, -191), "Club Terry");
FP_Entity_CreateAirportTerry(game, Dqn_V2_InitNx2(+200, -191), "Airport Terry");
FP_Entity_CreateKennelTerry(game, Dqn_V2_InitNx2(-300, -191), "Kennel Terry");
FP_Entity_CreateChurchTerry(game, Dqn_V2_InitNx2(-800, -191), "Church Terry");
game->tile_size = 37;
Dqn_V2I max_tile = platform->core.window_size / game->tile_size;
@@ -1092,8 +1099,10 @@ void FP_EntityActionStateMachine(FP_Game *game, TELY_Audio *audio, TELY_Platform
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;
FP_GameEntity *patron = FP_Game_GetEntity(game, entity->building_patron);
patron->flags &= ~(FP_GameEntityFlag_OccupiedInBuilding | FP_GameEntityFlag_RespondsToBuildings | FP_GameEntityFlag_PointOfInterestHeart);
patron->faction = FP_GameEntityFaction_Friendly;
patron->converted_faction = true;
}
entity->building_patron = {};
FP_Game_EntityTransitionState(game, entity, FP_EntityChurchTerryState_Idle);
@@ -1361,22 +1370,44 @@ void FP_Update(TELY_Platform *platform, FP_Game *game, TELY_PlatformInput *input
if (acceleration_meters_per_s.x == 0 && acceleration_meters_per_s.y == 0) {
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);
if (entity->flags & FP_GameEntityFlag_Aggros && entity->faction != FP_GameEntityFaction_Nil) {
FP_GameFindClosestEntityResult closest_defender = {};
closest_defender.dist_squared = DQN_F32_MAX;
closest_defender.pos = Dqn_V2_InitNx1(DQN_F32_MAX);
if (closest_result.dist_squared < DQN_SQUARED(aggro_dist_threshold)) {
bool has_waypoint_to_terry = false;
FP_GameEntityFaction enemy_faction =
entity->faction == FP_GameEntityFaction_Friendly
? FP_GameEntityFaction_Foe
: FP_GameEntityFaction_Friendly;
for (FP_GameEntityIterator defender_it = {}; FP_Game_DFSPostOrderWalkEntityTree(game, &defender_it, game->root_entity); ) {
FP_GameEntity *it_entity = defender_it.entity;
Dqn_V2 pos = FP_Game_CalcEntityWorldPos(game, it_entity->handle);
Dqn_f32 dist = Dqn_V2_LengthSq_V2x2(pos, entity_pos);
if (it_entity->faction != enemy_faction)
continue;
if (dist < closest_defender.dist_squared) {
closest_defender.pos = pos;
closest_defender.dist_squared = dist;
closest_defender.entity = it_entity->handle;
}
}
Dqn_f32 aggro_dist_threshold = FP_Game_MetersToPixelsNx1(game, 4.f);
if (closest_defender.dist_squared < DQN_SQUARED(aggro_dist_threshold)) {
bool has_waypoint_to_defender = false;
for (FP_SentinelListLink<FP_GameWaypoint> *link = nullptr;
!has_waypoint_to_terry && FP_SentinelList_Iterate<FP_GameWaypoint>(&entity->waypoints, &link); ) {
has_waypoint_to_terry = link->data.entity == closest_result.entity;
!has_waypoint_to_defender && FP_SentinelList_Iterate<FP_GameWaypoint>(&entity->waypoints, &link); ) {
has_waypoint_to_defender = link->data.entity == closest_defender.entity;
}
if (!has_waypoint_to_terry) {
FP_GameEntity *terry = FP_Game_GetEntity(game, closest_result.entity);
if (!has_waypoint_to_defender) {
FP_GameEntity *defender = FP_Game_GetEntity(game, closest_defender.entity);
FP_GameDirection aggro_direction = FP_GameDirection_Count;
DQN_FOR_UINDEX(dir_index, FP_GameDirection_Count) {
FP_GameEntityHandle slot_entity_handle = terry->aggro_slot[dir_index];
FP_GameEntityHandle slot_entity_handle = defender->aggro_slot[dir_index];
FP_GameEntity *slot_entity = FP_Game_GetEntity(game, slot_entity_handle);
if (FP_Game_IsNilEntity(slot_entity)) {
aggro_direction = DQN_CAST(FP_GameDirection)dir_index;
@@ -1388,21 +1419,11 @@ void FP_Update(TELY_Platform *platform, FP_Game *game, TELY_PlatformInput *input
FP_SentinelList_Front(&entity->waypoints),
game->chunk_pool);
FP_GameWaypoint *waypoint = &link->data;
waypoint->entity = terry->handle;
waypoint->entity = defender->handle;
if (aggro_direction != FP_GameDirection_Count) {
waypoint->type = FP_GameWaypointType_Side;
waypoint->type_direction = aggro_direction;
terry->aggro_slot[aggro_direction] = entity->handle;
}
}
} else {
if (closest_result.dist_squared > DQN_SQUARED(aggro_dist_threshold * 2.f)) {
for (FP_SentinelListLink<FP_GameWaypoint> *link = nullptr; FP_SentinelList_Iterate<FP_GameWaypoint>(&entity->waypoints, &link); ) {
FP_GameEntity *maybe_terry = FP_Game_GetEntity(game, link->data.entity);
if (maybe_terry->type == FP_EntityType_Terry) {
link = FP_SentinelList_Erase(&entity->waypoints, link, game->chunk_pool);
}
waypoint->type = FP_GameWaypointType_Side;
waypoint->type_direction = aggro_direction;
defender->aggro_slot[aggro_direction] = entity->handle;
}
}
}
@@ -1420,6 +1441,11 @@ void FP_Update(TELY_Platform *platform, FP_Game *game, TELY_PlatformInput *input
it_entity->type != FP_EntityType_ChurchTerry)
continue;
// NOTE: Already converted, we cannot attend church again
if (entity->converted_faction && it_entity->type == FP_EntityType_ChurchTerry) {
continue;
}
bool already_visited_building = false;
for (FP_SentinelListLink<FP_GameEntityHandle> *link_it = {};
!already_visited_building && FP_SentinelList_Iterate(&entity->buildings_visited, &link_it);
@@ -1459,15 +1485,26 @@ void FP_Update(TELY_Platform *platform, FP_Game *game, TELY_PlatformInput *input
waypoint->entity = closest_building.entity;
waypoint->type = FP_GameWaypointType_Side;
if (entity_pos.x <= top_left.x) {
waypoint->type_direction = FP_GameDirection_Left;
} else if (entity_pos.x >= top_right.x) {
waypoint->type_direction = FP_GameDirection_Right;
} else if (entity_pos.y <= top_left.y) {
waypoint->type_direction = FP_GameDirection_Up;
} else {
waypoint->type_direction = FP_GameDirection_Down;
uint32_t *direction_hit_count = nullptr;
FP_GameDirection least_encountered_direction = FP_GameDirection_Down;
DQN_FOR_UINDEX(dir_index, FP_GameDirection_Count) {
FP_GameEntity *waypoint_entity = FP_Game_GetEntity(game, waypoint->entity);
uint32_t *hit_count = waypoint_entity->count_of_entities_targetting_sides + dir_index;
if (!direction_hit_count || *hit_count < *direction_hit_count) {
direction_hit_count = hit_count;
least_encountered_direction = DQN_CAST(FP_GameDirection)dir_index;
} else if (hit_count == direction_hit_count) {
if (Dqn_PCG32_NextF32(&game->rng) >= 0.5f) {
direction_hit_count = hit_count;
least_encountered_direction = DQN_CAST(FP_GameDirection)dir_index;
}
}
}
waypoint->type_direction = least_encountered_direction;
(*direction_hit_count)++;
}
}
}
@@ -1490,17 +1527,7 @@ void FP_Update(TELY_Platform *platform, FP_Game *game, TELY_PlatformInput *input
FP_SentinelListLink<FP_GameWaypoint> *link = FP_SentinelList_MakeBefore(&entity->waypoints, FP_SentinelList_Front(&entity->waypoints), game->chunk_pool);
FP_GameWaypoint *waypoint = &link->data;
waypoint->entity = closest_heart.entity;
waypoint->type = FP_GameWaypointType_Side;
if (entity_pos.x <= club_top_left.x) {
waypoint->type_direction = FP_GameDirection_Left;
} else if (entity_pos.x >= club_top_right.x) {
waypoint->type_direction = FP_GameDirection_Right;
} else if (entity_pos.y <= club_top_left.y) {
waypoint->type_direction = FP_GameDirection_Up;
} else {
waypoint->type_direction = FP_GameDirection_Down;
}
waypoint->type = FP_GameWaypointType_ClosestSide;
}
}
}
@@ -1515,7 +1542,7 @@ void FP_Update(TELY_Platform *platform, FP_Game *game, TELY_PlatformInput *input
}
// NOTE: We found a waypoint that is valid to move towards
Dqn_V2 target_pos = FP_Game_CalcWaypointWorldPos(game, waypoint);
Dqn_V2 target_pos = FP_Game_CalcWaypointWorldPos(game, entity->handle, waypoint);
Dqn_V2 entity_to_waypoint = target_pos - entity_pos;
// NOTE: Check if we've arrived at the waypoint
@@ -1540,16 +1567,20 @@ 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 = false;
if (entity->flags & FP_GameEntityFlag_Aggros) {
aggro |= entity->faction == FP_GameEntityFaction_Friendly && waypoint_entity->faction & FP_GameEntityFaction_Foe;
aggro |= entity->faction == FP_GameEntityFaction_Foe && waypoint_entity->faction & FP_GameEntityFaction_Friendly;
}
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;
if (((waypoint->flags & FP_GameWaypointFlag_NonInterruptible) == 0) && (aggro_on_terry || building_response || heart_response)) {
if (((waypoint->flags & FP_GameWaypointFlag_NonInterruptible) == 0) && (aggro || 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 (building_response) {
FP_GameEntity *building = waypoint_entity;
if (FP_Game_IsNilEntityHandle(game, building->building_patron)) {
@@ -1632,8 +1663,7 @@ void FP_Update(TELY_Platform *platform, FP_Game *game, TELY_PlatformInput *input
}
}
// NOTE: Aggro makes the entity attack Terry, we will
// exit here preserving the waypoint in the entity.
// NOTE: Aggro makes the entity attack, we will exit here preserving the waypoint in the entity.
break;
} else {
FP_SentinelList_Erase(&entity->waypoints, waypoint_link, game->chunk_pool);
@@ -1812,7 +1842,7 @@ void FP_Update(TELY_Platform *platform, FP_Game *game, TELY_PlatformInput *input
// NOTE: Setup the mob with waypoints
FP_GameEntity *mob = FP_Game_GetEntity(game, link->data);
mob->waypoints = FP_SentinelList_Init<FP_GameWaypoint>(game->chunk_pool);
mob->flags |= FP_GameEntityFlag_AggrosWhenNearTerry;
mob->flags |= FP_GameEntityFlag_Aggros;
mob->flags |= FP_GameEntityFlag_RespondsToBuildings;
mob->hp_cap += hp_adjustment;
mob->hp = entity->hp_cap;
@@ -1850,6 +1880,11 @@ void FP_Update(TELY_Platform *platform, FP_Game *game, TELY_PlatformInput *input
Dqn_Rect attacker_box = FP_Game_CalcEntityAttackWorldHitBox(game, attacker->handle);
Dqn_V2 attacker_world_pos = FP_Game_CalcEntityWorldPos(game, attacker->handle);
FP_GameEntityFaction enemy_faction =
entity->faction == FP_GameEntityFaction_Friendly
? FP_GameEntityFaction_Foe
: FP_GameEntityFaction_Friendly;
for (FP_GameEntityIterator defender_it = {}; FP_Game_DFSPostOrderWalkEntityTree(game, &defender_it, game->root_entity); ) {
FP_GameEntity *defender = defender_it.entity;
if (defender->handle == attacker->handle)
@@ -1862,40 +1897,7 @@ void FP_Update(TELY_Platform *platform, FP_Game *game, TELY_PlatformInput *input
if (attacker->projectile_owner == defender->handle)
continue;
bool permit_attack = true;
switch (attacker->type) {
case FP_EntityType_Smoochie: {
if (defender->type == FP_EntityType_Smoochie || defender->type == FP_EntityType_Clinger || defender->type == FP_EntityType_Catfish)
permit_attack = false;
} break;
case FP_EntityType_Catfish: {
if (defender->type == FP_EntityType_Smoochie || defender->type == FP_EntityType_Clinger || defender->type == FP_EntityType_Catfish)
permit_attack = false;
} break;
case FP_EntityType_Clinger: {
if (defender->type == FP_EntityType_Smoochie || defender->type == FP_EntityType_Clinger || defender->type == FP_EntityType_Catfish)
permit_attack = false;
} break;
case FP_EntityType_Nil: break;
case FP_EntityType_Terry: break;
case FP_EntityType_Count: break;
case FP_EntityType_ClubTerry: break;
case FP_EntityType_Map: break;
case FP_EntityType_MerchantTerry: break;
case FP_EntityType_MerchantGraveyard: break;
case FP_EntityType_MerchantGym: break;
case FP_EntityType_MerchantPhoneCompany: break;
case FP_EntityType_Heart: break;
case FP_EntityType_AirportTerry:
case FP_EntityType_ChurchTerry:
case FP_EntityType_KennelTerry: break;
case FP_EntityType_PhoneMessageProjectile: break;
}
if (!permit_attack)
if (defender->faction != enemy_faction)
continue;
Dqn_Rect defender_box = FP_Game_CalcEntityWorldHitBox(game, defender->handle);
@@ -2050,6 +2052,12 @@ void FP_Render(FP_Game *game, TELY_Platform *platform, TELY_Renderer *renderer)
Dqn_V2_Zero /*rotate origin*/,
0.f /*rotate radians*/,
TELY_Colour_V4Alpha(TELY_COLOUR_WHITE_V4, entity->action.sprite_alpha));
if (entity->converted_faction) {
Dqn_V2 label_p = Dqn_Rect_InterpolatedPoint(dest_rect, Dqn_V2_InitNx2(0, -0.25f));
TELY_Render_TextF(renderer, label_p, Dqn_V2_InitNx2(0.f, 0.1f), "CONVERTED");
}
}
DQN_FOR_UINDEX(anim_index, entity->extra_cosmetic_anims.size) {
@@ -2175,7 +2183,7 @@ void FP_Render(FP_Game *game, TELY_Platform *platform, TELY_Renderer *renderer)
// NOTE: Draw the waypoints that the entity is moving along
Dqn_V2 start = world_pos;
for (FP_SentinelListLink<FP_GameWaypoint> *link = nullptr; FP_SentinelList_Iterate(&entity->waypoints, &link); ) {
Dqn_V2 end = FP_Game_CalcWaypointWorldPos(game, &link->data);
Dqn_V2 end = FP_Game_CalcWaypointWorldPos(game, entity->handle, &link->data);
TELY_Render_LineColourV4(renderer, start, end, TELY_COLOUR_WHITE_PALE_GOLDENROD_V4, 2.f);
start = end;
}
@@ -2194,6 +2202,14 @@ void FP_Render(FP_Game *game, TELY_Platform *platform, TELY_Renderer *renderer)
TELY_Render_TextF(renderer, draw_p, Dqn_V2_InitNx2(0.f, 1), "Hit Box Size: %.1fx%.1f", world_hit_box.size.x, world_hit_box.size.y); draw_p.y += line_height;
TELY_Render_TextF(renderer, draw_p, Dqn_V2_InitNx2(0.f, 1), "Tile: %I32dx%I32d", player_tile.x, player_tile.y); draw_p.y += line_height;
TELY_Render_TextF(renderer, draw_p, Dqn_V2_InitNx2(0.f, 1), "World Mouse Pos: (%.1f, %.1f)", world_mouse_p.x, world_mouse_p.y); draw_p.y += line_height;
Dqn_String8 faction = {};
switch (entity->faction) {
case FP_GameEntityFaction_Nil: faction = DQN_STRING8("Nil"); break;
case FP_GameEntityFaction_Friendly: faction = DQN_STRING8("Friendly"); break;
case FP_GameEntityFaction_Foe: faction = DQN_STRING8("Foe"); break;
}
TELY_Render_TextF(renderer, draw_p, Dqn_V2_InitNx2(0.f, 1), "Faction: %.*s", DQN_STRING_FMT(faction)); draw_p.y += line_height;
}
}
}