fp: Improve swarming of buildings
This commit is contained in:
+104
-88
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user