fp: Improve swarming of buildings

This commit is contained in:
doyle 2023-10-07 19:14:09 +11:00
parent 11cab9a726
commit d564ad225d
4 changed files with 159 additions and 111 deletions

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) { if (collider->type == FP_EntityType_Smoochie || collider->type == FP_EntityType_Clinger || collider->type == FP_EntityType_Catfish) {
entity_collides_with_collider = false; entity_collides_with_collider = false;
} else if (FP_Entity_IsBuildingForMobs(collider)) { } else if (FP_Entity_IsBuildingForMobs(collider)) {
#if 0
// NOTE: We disable collision on buildings we have visited to avoid some // NOTE: We disable collision on buildings we have visited to avoid some
// problems ... // problems ...
if (FP_SentinelList_Find(&entity->buildings_visited, collider->handle)) { if (FP_SentinelList_Find(&entity->buildings_visited, collider->handle)) {
entity_collides_with_collider = false; entity_collides_with_collider = false;
} }
#else
entity_collides_with_collider = false;
#endif
} }
} break; } break;
@ -439,7 +443,10 @@ void TELY_DLL_Init(void *user_data)
game->merchant_phone_company = FP_Entity_CreateMerchantPhoneCompany(game, base_top_right, "PhoneCompany"); 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; game->tile_size = 37;
Dqn_V2I max_tile = platform->core.window_size / game->tile_size; Dqn_V2I max_tile = platform->core.window_size / game->tile_size;
@ -1093,7 +1100,9 @@ void FP_EntityActionStateMachine(FP_Game *game, TELY_Audio *audio, TELY_Platform
if (action_has_finished) { if (action_has_finished) {
if (!FP_Game_IsNilEntityHandle(game, entity->building_patron)) { if (!FP_Game_IsNilEntityHandle(game, entity->building_patron)) {
FP_GameEntity *patron = FP_Game_GetEntity(game, entity->building_patron); FP_GameEntity *patron = FP_Game_GetEntity(game, entity->building_patron);
patron->flags &= ~FP_GameEntityFlag_OccupiedInBuilding; patron->flags &= ~(FP_GameEntityFlag_OccupiedInBuilding | FP_GameEntityFlag_RespondsToBuildings | FP_GameEntityFlag_PointOfInterestHeart);
patron->faction = FP_GameEntityFaction_Friendly;
patron->converted_faction = true;
} }
entity->building_patron = {}; entity->building_patron = {};
FP_Game_EntityTransitionState(game, entity, FP_EntityChurchTerryState_Idle); 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) { if (acceleration_meters_per_s.x == 0 && acceleration_meters_per_s.y == 0) {
Dqn_V2 entity_pos = FP_Game_CalcEntityWorldPos(game, entity->handle); Dqn_V2 entity_pos = FP_Game_CalcEntityWorldPos(game, entity->handle);
if (entity->flags & FP_GameEntityFlag_AggrosWhenNearTerry) { if (entity->flags & FP_GameEntityFlag_Aggros && entity->faction != FP_GameEntityFaction_Nil) {
FP_GameFindClosestEntityResult closest_result = FP_Game_FindClosestEntityWithType(game, entity->handle, FP_EntityType_Terry); FP_GameFindClosestEntityResult closest_defender = {};
Dqn_f32 aggro_dist_threshold = FP_Game_MetersToPixelsNx1(game, 4.f); 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)) { FP_GameEntityFaction enemy_faction =
bool has_waypoint_to_terry = false; entity->faction == FP_GameEntityFaction_Friendly
for (FP_SentinelListLink<FP_GameWaypoint> *link = nullptr; ? FP_GameEntityFaction_Foe
!has_waypoint_to_terry && FP_SentinelList_Iterate<FP_GameWaypoint>(&entity->waypoints, &link); ) { : FP_GameEntityFaction_Friendly;
has_waypoint_to_terry = link->data.entity == closest_result.entity;
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;
}
} }
if (!has_waypoint_to_terry) { Dqn_f32 aggro_dist_threshold = FP_Game_MetersToPixelsNx1(game, 4.f);
FP_GameEntity *terry = FP_Game_GetEntity(game, closest_result.entity); 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_defender && FP_SentinelList_Iterate<FP_GameWaypoint>(&entity->waypoints, &link); ) {
has_waypoint_to_defender = link->data.entity == closest_defender.entity;
}
if (!has_waypoint_to_defender) {
FP_GameEntity *defender = FP_Game_GetEntity(game, closest_defender.entity);
FP_GameDirection aggro_direction = FP_GameDirection_Count; FP_GameDirection aggro_direction = FP_GameDirection_Count;
DQN_FOR_UINDEX(dir_index, 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); FP_GameEntity *slot_entity = FP_Game_GetEntity(game, slot_entity_handle);
if (FP_Game_IsNilEntity(slot_entity)) { if (FP_Game_IsNilEntity(slot_entity)) {
aggro_direction = DQN_CAST(FP_GameDirection)dir_index; 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), FP_SentinelList_Front(&entity->waypoints),
game->chunk_pool); game->chunk_pool);
FP_GameWaypoint *waypoint = &link->data; FP_GameWaypoint *waypoint = &link->data;
waypoint->entity = terry->handle; waypoint->entity = defender->handle;
if (aggro_direction != FP_GameDirection_Count) { if (aggro_direction != FP_GameDirection_Count) {
waypoint->type = FP_GameWaypointType_Side; waypoint->type = FP_GameWaypointType_Side;
waypoint->type_direction = aggro_direction; waypoint->type_direction = aggro_direction;
terry->aggro_slot[aggro_direction] = entity->handle; defender->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);
}
} }
} }
} }
@ -1420,6 +1441,11 @@ void FP_Update(TELY_Platform *platform, FP_Game *game, TELY_PlatformInput *input
it_entity->type != FP_EntityType_ChurchTerry) it_entity->type != FP_EntityType_ChurchTerry)
continue; 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; bool already_visited_building = false;
for (FP_SentinelListLink<FP_GameEntityHandle> *link_it = {}; for (FP_SentinelListLink<FP_GameEntityHandle> *link_it = {};
!already_visited_building && FP_SentinelList_Iterate(&entity->buildings_visited, &link_it); !already_visited_building && FP_SentinelList_Iterate(&entity->buildings_visited, &link_it);
@ -1459,17 +1485,28 @@ void FP_Update(TELY_Platform *platform, FP_Game *game, TELY_PlatformInput *input
waypoint->entity = closest_building.entity; waypoint->entity = closest_building.entity;
waypoint->type = FP_GameWaypointType_Side; waypoint->type = FP_GameWaypointType_Side;
if (entity_pos.x <= top_left.x) {
waypoint->type_direction = FP_GameDirection_Left; uint32_t *direction_hit_count = nullptr;
} else if (entity_pos.x >= top_right.x) { FP_GameDirection least_encountered_direction = FP_GameDirection_Down;
waypoint->type_direction = FP_GameDirection_Right;
} else if (entity_pos.y <= top_left.y) { DQN_FOR_UINDEX(dir_index, FP_GameDirection_Count) {
waypoint->type_direction = FP_GameDirection_Up; FP_GameEntity *waypoint_entity = FP_Game_GetEntity(game, waypoint->entity);
} else { uint32_t *hit_count = waypoint_entity->count_of_entities_targetting_sides + dir_index;
waypoint->type_direction = FP_GameDirection_Down; 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)++;
}
}
} }
if (entity->flags & FP_GameEntityFlag_PointOfInterestHeart) { if (entity->flags & FP_GameEntityFlag_PointOfInterestHeart) {
@ -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_SentinelListLink<FP_GameWaypoint> *link = FP_SentinelList_MakeBefore(&entity->waypoints, FP_SentinelList_Front(&entity->waypoints), game->chunk_pool);
FP_GameWaypoint *waypoint = &link->data; FP_GameWaypoint *waypoint = &link->data;
waypoint->entity = closest_heart.entity; waypoint->entity = closest_heart.entity;
waypoint->type = FP_GameWaypointType_Side; waypoint->type = FP_GameWaypointType_ClosestSide;
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;
}
} }
} }
} }
@ -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 // 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; Dqn_V2 entity_to_waypoint = target_pos - entity_pos;
// NOTE: Check if we've arrived at the waypoint // 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 // 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) && bool building_response = (entity->flags & FP_GameEntityFlag_RespondsToBuildings) &&
waypoint_entity->type == FP_EntityType_ClubTerry || waypoint_entity->type == FP_EntityType_ClubTerry ||
waypoint_entity->type == FP_EntityType_AirportTerry || waypoint_entity->type == FP_EntityType_AirportTerry ||
waypoint_entity->type == FP_EntityType_ChurchTerry; waypoint_entity->type == FP_EntityType_ChurchTerry;
bool heart_response = (entity->flags & FP_GameEntityFlag_PointOfInterestHeart) && waypoint_entity->type == FP_EntityType_Heart; 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 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) { if (building_response) {
FP_GameEntity *building = waypoint_entity; FP_GameEntity *building = waypoint_entity;
if (FP_Game_IsNilEntityHandle(game, building->building_patron)) { 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 // NOTE: Aggro makes the entity attack, we will exit here preserving the waypoint in the entity.
// exit here preserving the waypoint in the entity.
break; break;
} else { } else {
FP_SentinelList_Erase(&entity->waypoints, waypoint_link, game->chunk_pool); 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 // NOTE: Setup the mob with waypoints
FP_GameEntity *mob = FP_Game_GetEntity(game, link->data); FP_GameEntity *mob = FP_Game_GetEntity(game, link->data);
mob->waypoints = FP_SentinelList_Init<FP_GameWaypoint>(game->chunk_pool); 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->flags |= FP_GameEntityFlag_RespondsToBuildings;
mob->hp_cap += hp_adjustment; mob->hp_cap += hp_adjustment;
mob->hp = entity->hp_cap; 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_Rect attacker_box = FP_Game_CalcEntityAttackWorldHitBox(game, attacker->handle);
Dqn_V2 attacker_world_pos = FP_Game_CalcEntityWorldPos(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); ) { for (FP_GameEntityIterator defender_it = {}; FP_Game_DFSPostOrderWalkEntityTree(game, &defender_it, game->root_entity); ) {
FP_GameEntity *defender = defender_it.entity; FP_GameEntity *defender = defender_it.entity;
if (defender->handle == attacker->handle) 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) if (attacker->projectile_owner == defender->handle)
continue; continue;
bool permit_attack = true; if (defender->faction != enemy_faction)
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)
continue; continue;
Dqn_Rect defender_box = FP_Game_CalcEntityWorldHitBox(game, defender->handle); 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*/, Dqn_V2_Zero /*rotate origin*/,
0.f /*rotate radians*/, 0.f /*rotate radians*/,
TELY_Colour_V4Alpha(TELY_COLOUR_WHITE_V4, entity->action.sprite_alpha)); 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) { 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 // NOTE: Draw the waypoints that the entity is moving along
Dqn_V2 start = world_pos; Dqn_V2 start = world_pos;
for (FP_SentinelListLink<FP_GameWaypoint> *link = nullptr; FP_SentinelList_Iterate(&entity->waypoints, &link); ) { 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); TELY_Render_LineColourV4(renderer, start, end, TELY_COLOUR_WHITE_PALE_GOLDENROD_V4, 2.f);
start = end; 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), "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), "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; 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;
} }
} }
} }

View File

@ -305,8 +305,9 @@ static FP_GameEntityHandle FP_Entity_CreateClinger(FP_Game *game, Dqn_V2 pos, DQ
entity->local_pos = pos; entity->local_pos = pos;
entity->sprite_height = FP_Entity_GetRenderData(game, entity->type, 0 /*state*/, FP_GameDirection_Down).height; entity->sprite_height = FP_Entity_GetRenderData(game, entity->type, 0 /*state*/, FP_GameDirection_Down).height;
entity->attack_cooldown_ms = 1000; entity->attack_cooldown_ms = 1000;
entity->faction = FP_GameEntityFaction_Foe;
entity->local_hit_box_size = FP_Game_MetersToPixelsNx2(game, 0.4f, entity->sprite_height.meters * .5f); entity->local_hit_box_size = FP_Game_MetersToPixelsNx2(game, 0.7f, entity->sprite_height.meters * .5f);
FP_Entity_AddDebugEditorFlags(game, entity->handle); FP_Entity_AddDebugEditorFlags(game, entity->handle);
entity->flags |= FP_GameEntityFlag_NonTraversable; entity->flags |= FP_GameEntityFlag_NonTraversable;
entity->flags |= FP_GameEntityFlag_Attackable; entity->flags |= FP_GameEntityFlag_Attackable;
@ -327,10 +328,11 @@ static FP_GameEntityHandle FP_Entity_CreateSmoochie(FP_Game *game, Dqn_V2 pos, D
entity->local_pos = pos; entity->local_pos = pos;
entity->sprite_height = FP_Entity_GetRenderData(game, entity->type, 0 /*state*/, FP_GameDirection_Down).height; entity->sprite_height = FP_Entity_GetRenderData(game, entity->type, 0 /*state*/, FP_GameDirection_Down).height;
entity->attack_cooldown_ms = 1000; entity->attack_cooldown_ms = 1000;
entity->local_hit_box_size = FP_Game_MetersToPixelsNx2(game, 0.4f, entity->sprite_height.meters * .6f); entity->local_hit_box_size = FP_Game_MetersToPixelsNx2(game, 0.7f, entity->sprite_height.meters * .6f);
FP_Entity_AddDebugEditorFlags(game, entity->handle); FP_Entity_AddDebugEditorFlags(game, entity->handle);
entity->flags |= FP_GameEntityFlag_NonTraversable; entity->flags |= FP_GameEntityFlag_NonTraversable;
entity->flags |= FP_GameEntityFlag_Attackable; entity->flags |= FP_GameEntityFlag_Attackable;
entity->faction = FP_GameEntityFaction_Foe;
return result; return result;
} }
@ -348,10 +350,11 @@ static FP_GameEntityHandle FP_Entity_CreateCatfish(FP_Game *game, Dqn_V2 pos, DQ
entity->local_pos = pos; entity->local_pos = pos;
entity->sprite_height = FP_Entity_GetRenderData(game, entity->type, 0 /*state*/, FP_GameDirection_Down).height; entity->sprite_height = FP_Entity_GetRenderData(game, entity->type, 0 /*state*/, FP_GameDirection_Down).height;
entity->attack_cooldown_ms = 1000; entity->attack_cooldown_ms = 1000;
entity->local_hit_box_size = FP_Game_MetersToPixelsNx2(game, 0.4f, entity->sprite_height.meters * .6f); entity->local_hit_box_size = FP_Game_MetersToPixelsNx2(game, 0.7f, entity->sprite_height.meters * .6f);
FP_Entity_AddDebugEditorFlags(game, entity->handle); FP_Entity_AddDebugEditorFlags(game, entity->handle);
entity->flags |= FP_GameEntityFlag_NonTraversable; entity->flags |= FP_GameEntityFlag_NonTraversable;
entity->flags |= FP_GameEntityFlag_Attackable; entity->flags |= FP_GameEntityFlag_Attackable;
entity->faction = FP_GameEntityFaction_Foe;
return result; return result;
} }
@ -433,6 +436,7 @@ static FP_GameEntityHandle FP_Entity_CreateTerry(FP_Game *game, Dqn_V2 pos, DQN_
entity->flags |= FP_GameEntityFlag_CameraTracking; entity->flags |= FP_GameEntityFlag_CameraTracking;
entity->terry_mobile_data_plan_cap = DQN_KILOBYTES(6); entity->terry_mobile_data_plan_cap = DQN_KILOBYTES(6);
entity->terry_mobile_data_plan = entity->terry_mobile_data_plan_cap; entity->terry_mobile_data_plan = entity->terry_mobile_data_plan_cap;
entity->faction = FP_GameEntityFaction_Friendly;
return result; return result;
} }
@ -676,6 +680,7 @@ static FP_GameEntityHandle FP_Entity_CreatePhoneMessageProjectile(FP_Game *game,
entity->attack_box_size = entity->local_hit_box_size; entity->attack_box_size = entity->local_hit_box_size;
entity->ttl_end_timestamp = game->clock_ms + 1000; entity->ttl_end_timestamp = game->clock_ms + 1000;
entity->projectile_owner = owner; entity->projectile_owner = owner;
entity->faction = FP_GameEntityFaction_Friendly;
uint64_t duration_ms = FP_GAME_ENTITY_ACTION_INFINITE_TIMER; uint64_t duration_ms = FP_GAME_ENTITY_ACTION_INFINITE_TIMER;
FP_Game_EntityActionReset(game, result, duration_ms, render_data.sprite); FP_Game_EntityActionReset(game, result, duration_ms, render_data.sprite);

View File

@ -667,7 +667,7 @@ static Dqn_Slice<Dqn_V2I> FP_Game_AStarPathFind(FP_Game *game,
return result; return result;
} }
static Dqn_V2 FP_Game_CalcWaypointWorldPos(FP_Game *game, FP_GameWaypoint const *waypoint) static Dqn_V2 FP_Game_CalcWaypointWorldPos(FP_Game *game, FP_GameEntityHandle src_entity, FP_GameWaypoint const *waypoint)
{ {
Dqn_V2 result = {}; Dqn_V2 result = {};
if (!game || !waypoint) if (!game || !waypoint)
@ -683,26 +683,38 @@ static Dqn_V2 FP_Game_CalcWaypointWorldPos(FP_Game *game, FP_GameWaypoint const
result = FP_Game_CalcEntityWorldPos(game, waypoint_entity->handle); result = FP_Game_CalcEntityWorldPos(game, waypoint_entity->handle);
} break; } break;
case FP_GameWaypointType_ClosestSide: /*FALLTHRU*/
case FP_GameWaypointType_Side: { case FP_GameWaypointType_Side: {
// NOTE: Sweep entity with half the radius of the source entity
Dqn_Rect src_rect = FP_Game_CalcEntityWorldHitBox(game, src_entity);
Dqn_Rect entity_rect = FP_Game_CalcEntityWorldHitBox(game, waypoint_entity->handle); Dqn_Rect entity_rect = FP_Game_CalcEntityWorldHitBox(game, waypoint_entity->handle);
switch (waypoint->type_direction) { entity_rect.pos -= (src_rect.size * .5f);
case FP_GameDirection_Up: { entity_rect.size += src_rect.size;
result = Dqn_V2_InitNx2(entity_rect.pos.x + entity_rect.size.w * .5f, entity_rect.pos.y - entity_rect.size.h * .1f);
} break;
case FP_GameDirection_Down: { Dqn_V2 side_pos_list[FP_GameDirection_Count] = {};
result = Dqn_V2_InitNx2(entity_rect.pos.x + entity_rect.size.w * .5f, entity_rect.pos.y + entity_rect.size.h + entity_rect.size.h * .1f); side_pos_list[FP_GameDirection_Up] = Dqn_V2_InitNx2(entity_rect.pos.x + entity_rect.size.w * .5f, entity_rect.pos.y - entity_rect.size.h * .1f);
} break; side_pos_list[FP_GameDirection_Down] = Dqn_V2_InitNx2(entity_rect.pos.x + entity_rect.size.w * .5f, entity_rect.pos.y + entity_rect.size.h + entity_rect.size.h * .1f);
side_pos_list[FP_GameDirection_Left] = Dqn_V2_InitNx2(entity_rect.pos.x - entity_rect.size.w * .1f, entity_rect.pos.y + entity_rect.size.h * .5f);
side_pos_list[FP_GameDirection_Right] = Dqn_V2_InitNx2(entity_rect.pos.x + entity_rect.size.w + entity_rect.size.w * .1f, entity_rect.pos.y + entity_rect.size.h * .5f);
case FP_GameDirection_Left: { if (waypoint->type == FP_GameWaypointType_Side) {
result = Dqn_V2_InitNx2(entity_rect.pos.x - entity_rect.size.w * .1f, entity_rect.pos.y + entity_rect.size.h * .5f); result = side_pos_list[waypoint->type_direction];
} break; } else {
Dqn_f32 best_dist = DQN_F32_MAX;
for (Dqn_V2 target_pos : side_pos_list) {
Dqn_f32 dist_squared = Dqn_V2_LengthSq_V2x2(src_rect.pos, target_pos);
if (dist_squared < best_dist) {
best_dist = dist_squared;
result = target_pos;
}
}
case FP_GameDirection_Right: { Dqn_f32 curr_dist_to_entity = Dqn_V2_LengthSq_V2x2(entity_rect.pos, src_rect.pos);
result = Dqn_V2_InitNx2(entity_rect.pos.x + entity_rect.size.w + entity_rect.size.w * .1f, entity_rect.pos.y + entity_rect.size.h * .5f); if (curr_dist_to_entity < best_dist) {
} break; // NOTE: We are already closer to the entity than the closest calculated side,
// we assume we're at the entity already.
case FP_GameDirection_Count: DQN_INVALID_CODE_PATH; break; result = FP_Game_CalcEntityWorldPos(game, src_entity);
}
} }
} break; } break;

View File

@ -14,7 +14,7 @@ enum FP_GameEntityFlag
FP_GameEntityFlag_NonTraversable = 1 << 6, FP_GameEntityFlag_NonTraversable = 1 << 6,
FP_GameEntityFlag_MobSpawner = 1 << 7, FP_GameEntityFlag_MobSpawner = 1 << 7,
FP_GameEntityFlag_MobSpawnerWaypoint = 1 << 8, FP_GameEntityFlag_MobSpawnerWaypoint = 1 << 8,
FP_GameEntityFlag_AggrosWhenNearTerry = 1 << 9, FP_GameEntityFlag_Aggros = 1 << 9,
FP_GameEntityFlag_Attackable = 1 << 10, FP_GameEntityFlag_Attackable = 1 << 10,
FP_GameEntityFlag_RespondsToBuildings = 1 << 11, FP_GameEntityFlag_RespondsToBuildings = 1 << 11,
FP_GameEntityFlag_OccupiedInBuilding = 1 << 12, FP_GameEntityFlag_OccupiedInBuilding = 1 << 12,
@ -22,6 +22,8 @@ enum FP_GameEntityFlag
FP_GameEntityFlag_CameraTracking = 1 << 14, FP_GameEntityFlag_CameraTracking = 1 << 14,
FP_GameEntityFlag_BuildZone = 1 << 15, FP_GameEntityFlag_BuildZone = 1 << 15,
FP_GameEntityFlag_TTL = 1 << 16, FP_GameEntityFlag_TTL = 1 << 16,
FP_GameEntityFlag_Friendly = 1 << 17,
FP_GameEntityFlag_Foe = 1 << 18,
}; };
enum FP_GameShapeType enum FP_GameShapeType
@ -73,6 +75,7 @@ enum FP_GameWaypointType
{ {
FP_GameWaypointType_At, // Move to the specified entity FP_GameWaypointType_At, // Move to the specified entity
FP_GameWaypointType_Side, // Move to the side of the entity specified by the direction FP_GameWaypointType_Side, // Move to the side of the entity specified by the direction
FP_GameWaypointType_ClosestSide, // Move to the side of the entity closest to us
FP_GameWaypointType_Offset, // Move to the designed offset from the entity FP_GameWaypointType_Offset, // Move to the designed offset from the entity
}; };
@ -152,6 +155,13 @@ struct FP_GameInventory
uint8_t churchs; uint8_t churchs;
}; };
enum FP_GameEntityFaction
{
FP_GameEntityFaction_Nil,
FP_GameEntityFaction_Friendly,
FP_GameEntityFaction_Foe,
};
struct FP_GameEntity struct FP_GameEntity
{ {
FP_GameEntity *next; FP_GameEntity *next;
@ -221,6 +231,11 @@ struct FP_GameEntity
uint16_t hp_cap; uint16_t hp_cap;
uint16_t stamina; uint16_t stamina;
uint16_t stamina_cap; uint16_t stamina_cap;
bool converted_faction;
FP_GameEntityFaction faction;
uint32_t count_of_entities_targetting_sides[FP_GameDirection_Count];
}; };
struct FP_GameEntityIterator struct FP_GameEntityIterator