Compare commits
2 Commits
634a7c8cb4
...
25736461fb
| Author | SHA1 | Date | |
|---|---|---|---|
| 25736461fb | |||
| a651bc7e14 |
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
Binary file not shown.
Vendored
+1
-1
Submodule External/tely updated: 595e3c7f1e...8d39ef2d83
+358
-282
@@ -187,6 +187,12 @@ static void FP_Game_MoveEntity(FP_Game *game, FP_GameEntityHandle entity_handle,
|
||||
#else
|
||||
entity_collides_with_collider = false;
|
||||
#endif
|
||||
} else if (collider->type == FP_EntityType_Heart ||
|
||||
collider->type == FP_EntityType_MerchantGym ||
|
||||
collider->type == FP_EntityType_MerchantTerry ||
|
||||
collider->type == FP_EntityType_MerchantGraveyard ||
|
||||
collider->type == FP_EntityType_MerchantPhoneCompany) {
|
||||
entity_collides_with_collider = false;
|
||||
}
|
||||
} break;
|
||||
|
||||
@@ -453,19 +459,20 @@ static void FP_PlayReset(FP_Game *game, TELY_Platform *platform)
|
||||
play->merchant_phone_company = FP_Entity_CreateMerchantPhoneCompany(game, base_top_right, "PhoneCompany");
|
||||
}
|
||||
|
||||
#if 0
|
||||
FP_Entity_CreateClubTerry(game, Dqn_V2_InitNx2(+500, -191), "Club Terry");
|
||||
FP_Entity_CreateKennelTerry(game, Dqn_V2_InitNx2(-300, -191), "Kennel Terry");
|
||||
FP_Entity_CreateChurchTerry(game, Dqn_V2_InitNx2(-800, -191), "Church Terry");
|
||||
FP_Entity_CreateAirportTerry(game, Dqn_V2_InitNx2(-1200, -191), "Airport Terry");
|
||||
#endif
|
||||
|
||||
play->tile_size = 37;
|
||||
Dqn_V2I max_tile = platform->core.window_size / play->tile_size;
|
||||
|
||||
// NOTE: Heart
|
||||
FP_Entity_CreateHeart(game, base_mid_p, "Heart");
|
||||
|
||||
play->camera.world_pos = base_mid_p - Dqn_V2_InitV2I(platform->core.window_size * .5f);
|
||||
play->camera.scale = Dqn_V2_InitNx1(1);
|
||||
game->play.heart = FP_Entity_CreateHeart(game, base_mid_p, "Heart");
|
||||
play->camera.world_pos = base_mid_p - Dqn_V2_InitV2I(platform->core.window_size * .5f);
|
||||
play->camera.scale = Dqn_V2_InitNx1(1);
|
||||
}
|
||||
|
||||
extern "C" __declspec(dllexport)
|
||||
@@ -501,6 +508,13 @@ void TELY_DLL_Init(void *user_data)
|
||||
game->audio[FP_GameAudio_Woosh] = platform->func_load_audio(assets, DQN_STRING8("Woosh"), DQN_STRING8("Data/Audio/woosh.ogg"));
|
||||
game->audio[FP_GameAudio_Ching] = platform->func_load_audio(assets, DQN_STRING8("Ching"), DQN_STRING8("Data/Audio/ching.ogg"));
|
||||
game->audio[FP_GameAudio_Church] = platform->func_load_audio(assets, DQN_STRING8("Church"), DQN_STRING8("Data/Audio/church.ogg"));
|
||||
game->audio[FP_GameAudio_Plane] = platform->func_load_audio(assets, DQN_STRING8("Plane"), DQN_STRING8("Data/Audio/airport.ogg"));
|
||||
game->audio[FP_GameAudio_Club] = platform->func_load_audio(assets, DQN_STRING8("Club"), DQN_STRING8("Data/Audio/club_terry.ogg"));
|
||||
game->audio[FP_GameAudio_Dog] = platform->func_load_audio(assets, DQN_STRING8("Dog"), DQN_STRING8("Data/Audio/dog.ogg"));
|
||||
game->audio[FP_GameAudio_MerchantTerry] = platform->func_load_audio(assets, DQN_STRING8("Door"), DQN_STRING8("Data/Audio/merchant_terry.ogg"));
|
||||
game->audio[FP_GameAudio_MerchantGhost] = platform->func_load_audio(assets, DQN_STRING8("Ghost"), DQN_STRING8("Data/Audio/merchant_ghost.ogg"));
|
||||
game->audio[FP_GameAudio_MerchantGym] = platform->func_load_audio(assets, DQN_STRING8("Gym"), DQN_STRING8("Data/Audio/merchant_gym.ogg"));
|
||||
game->audio[FP_GameAudio_MerchantPhone] = platform->func_load_audio(assets, DQN_STRING8("Phone"), DQN_STRING8("Data/Audio/merchant_tech.ogg"));
|
||||
|
||||
platform->user_data = game;
|
||||
{
|
||||
@@ -1136,7 +1150,7 @@ void FP_EntityActionStateMachine(FP_Game *game, TELY_Audio *audio, TELY_Platform
|
||||
if (entity->waypoints.size == 0) {
|
||||
FP_GameEntity *patron = FP_Game_GetEntity(game, entity->building_patron);
|
||||
patron->local_pos = entity->local_pos;
|
||||
patron->flags &= ~(FP_GameEntityFlag_OccupiedInBuilding | FP_GameEntityFlag_PointOfInterestHeart);
|
||||
patron->flags &= ~(FP_GameEntityFlag_OccupiedInBuilding);
|
||||
FP_Game_DeleteEntity(game, entity->handle);
|
||||
return;
|
||||
}
|
||||
@@ -1232,7 +1246,7 @@ 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_GameEntityFlag_RespondsToBuildings | FP_GameEntityFlag_PointOfInterestHeart);
|
||||
patron->flags &= ~(FP_GameEntityFlag_OccupiedInBuilding | FP_GameEntityFlag_RespondsToBuildings);
|
||||
patron->faction = FP_GameEntityFaction_Friendly;
|
||||
patron->converted_faction = true;
|
||||
}
|
||||
@@ -1508,302 +1522,279 @@ void FP_Update(TELY_Platform *platform, FP_Game *game, TELY_PlatformInput *input
|
||||
|
||||
// NOTE: Determine AI movement =============================================================
|
||||
Dqn_V2 entity_pos = FP_Game_CalcEntityWorldPos(game, entity->handle);
|
||||
if (acceleration_meters_per_s.x == 0 && acceleration_meters_per_s.y == 0) {
|
||||
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 (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);
|
||||
|
||||
FP_GameEntityFaction enemy_faction =
|
||||
entity->faction == FP_GameEntityFaction_Friendly
|
||||
? FP_GameEntityFaction_Foe
|
||||
: FP_GameEntityFaction_Friendly;
|
||||
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->play.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);
|
||||
for (FP_GameEntityIterator defender_it = {}; FP_Game_DFSPostOrderWalkEntityTree(game, &defender_it, game->play.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 (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->play, 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_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_SentinelListLink<FP_GameWaypoint> *link = FP_SentinelList_MakeBefore(&entity->waypoints,
|
||||
FP_SentinelList_Front(&entity->waypoints),
|
||||
game->play.chunk_pool);
|
||||
FP_GameWaypoint *waypoint = &link->data;
|
||||
waypoint->entity = defender->handle;
|
||||
waypoint->type = FP_GameWaypointType_ClosestSide;
|
||||
}
|
||||
} else {
|
||||
if (closest_defender.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->play.chunk_pool);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (dist < closest_defender.dist_squared) {
|
||||
closest_defender.pos = pos;
|
||||
closest_defender.dist_squared = dist;
|
||||
closest_defender.entity = it_entity->handle;
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
for (FP_GameEntityIterator building_it = {}; FP_Game_DFSPostOrderWalkEntityTree(game, &building_it, game->play.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;
|
||||
|
||||
// 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);
|
||||
) {
|
||||
FP_GameEntityHandle visit_item = link_it->data;
|
||||
already_visited_building = visit_item == it_entity->handle;
|
||||
}
|
||||
|
||||
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;
|
||||
Dqn_f32 aggro_dist_threshold = FP_Game_MetersToPixelsNx1(game->play, 4.f);
|
||||
Dqn_f32 dist_to_defender = DQN_SQRTF(closest_defender.dist_squared);
|
||||
if (dist_to_defender > (aggro_dist_threshold * 1.5f)) {
|
||||
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->play.chunk_pool);
|
||||
}
|
||||
}
|
||||
} else if (dist_to_defender < 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 (!FP_Game_IsNilEntityHandle(game, closest_building.entity) &&
|
||||
closest_building.dist_squared < DQN_SQUARED(FP_Game_MetersToPixelsNx1(game->play, 5.f))) {
|
||||
|
||||
bool has_waypoint_to_building = false;
|
||||
for (FP_SentinelListLink<FP_GameWaypoint> *link = nullptr;
|
||||
!has_waypoint_to_building && FP_SentinelList_Iterate<FP_GameWaypoint>(&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<FP_GameWaypoint> *link = FP_SentinelList_MakeBefore(&entity->waypoints, FP_SentinelList_Front(&entity->waypoints), game->play.chunk_pool);
|
||||
FP_GameWaypoint *waypoint = &link->data;
|
||||
waypoint->entity = closest_building.entity;
|
||||
waypoint->type = FP_GameWaypointType_Side;
|
||||
|
||||
|
||||
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->play.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 (!has_waypoint_to_defender) {
|
||||
FP_GameEntity *defender = FP_Game_GetEntity(game, closest_defender.entity);
|
||||
FP_SentinelListLink<FP_GameWaypoint> *link = FP_SentinelList_MakeBefore(&entity->waypoints,
|
||||
FP_SentinelList_Front(&entity->waypoints),
|
||||
game->play.chunk_pool);
|
||||
FP_GameWaypoint *waypoint = &link->data;
|
||||
waypoint->entity = defender->handle;
|
||||
waypoint->type = FP_GameWaypointType_ClosestSide;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (entity->flags & FP_GameEntityFlag_PointOfInterestHeart) {
|
||||
FP_GameFindClosestEntityResult closest_heart = FP_Game_FindClosestEntityWithType(game, entity->handle, FP_EntityType_Heart);
|
||||
if (closest_heart.dist_squared < DQN_SQUARED(FP_Game_MetersToPixelsNx1(game->play, 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 = false;
|
||||
for (FP_SentinelListLink<FP_GameWaypoint> *link = nullptr;
|
||||
!has_waypoint_to && FP_SentinelList_Iterate<FP_GameWaypoint>(&entity->waypoints, &link); ) {
|
||||
has_waypoint_to = link->data.entity == closest_heart.entity;
|
||||
}
|
||||
for (FP_GameEntityIterator building_it = {}; FP_Game_DFSPostOrderWalkEntityTree(game, &building_it, game->play.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;
|
||||
|
||||
if (!has_waypoint_to) {
|
||||
Dqn_Rect club_hit_box = FP_Game_CalcEntityWorldHitBox(game, closest_heart.entity);
|
||||
Dqn_V2 club_top_left = Dqn_Rect_TopLeft(club_hit_box);
|
||||
Dqn_V2 club_top_right = Dqn_Rect_TopRight(club_hit_box);
|
||||
|
||||
FP_SentinelListLink<FP_GameWaypoint> *link = FP_SentinelList_MakeBefore(&entity->waypoints, FP_SentinelList_Front(&entity->waypoints), game->play.chunk_pool);
|
||||
FP_GameWaypoint *waypoint = &link->data;
|
||||
waypoint->entity = closest_heart.entity;
|
||||
waypoint->type = FP_GameWaypointType_ClosestSide;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
while (entity->waypoints.size) {
|
||||
FP_SentinelListLink<FP_GameWaypoint> *waypoint_link = entity->waypoints.sentinel->next;
|
||||
FP_GameWaypoint const *waypoint = &waypoint_link->data;
|
||||
FP_GameEntity *waypoint_entity = FP_Game_GetEntity(game, waypoint_link->data.entity);
|
||||
if (FP_Game_IsNilEntity(waypoint_entity)) {
|
||||
FP_SentinelList_Erase(&entity->waypoints, waypoint_link, game->play.chunk_pool);
|
||||
// NOTE: Already converted, we cannot attend church again
|
||||
if (entity->converted_faction && it_entity->type == FP_EntityType_ChurchTerry) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// NOTE: We found a waypoint that is valid to move towards
|
||||
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
|
||||
Dqn_f32 dist_to_waypoint_sq = Dqn_V2_LengthSq(entity_to_waypoint);
|
||||
|
||||
Dqn_f32 arrival_threshold = {};
|
||||
switch (waypoint->arrive) {
|
||||
case FP_GameWaypointArrive_Default: {
|
||||
Dqn_Rect waypoint_hit_box = FP_Game_CalcEntityWorldHitBox(game, waypoint_entity->handle);
|
||||
arrival_threshold = waypoint_hit_box.size.w * .5f;
|
||||
} break;
|
||||
|
||||
case FP_GameWaypointArrive_WhenWithinEntitySize: {
|
||||
arrival_threshold = DQN_MAX(waypoint_entity->local_hit_box_size.w, waypoint_entity->local_hit_box_size.h) * waypoint_link->data.value;
|
||||
} break;
|
||||
bool already_visited_building = false;
|
||||
for (FP_SentinelListLink<FP_GameEntityHandle> *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;
|
||||
}
|
||||
|
||||
// NOTE: We haven't arrived yet, calculate an acceleration vector to the waypoint
|
||||
if (dist_to_waypoint_sq > DQN_SQUARED(arrival_threshold)) {
|
||||
Dqn_V2 entity_to_waypoint_norm = Dqn_V2_Normalise(entity_to_waypoint);
|
||||
acceleration_meters_per_s = entity_to_waypoint_norm * (entity->base_acceleration_per_s.meters * 0.25f);
|
||||
break;
|
||||
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->play, 5.f))) {
|
||||
|
||||
bool has_waypoint_to_building = false;
|
||||
for (FP_SentinelListLink<FP_GameWaypoint> *link = nullptr;
|
||||
!has_waypoint_to_building && FP_SentinelList_Iterate<FP_GameWaypoint>(&entity->waypoints, &link); ) {
|
||||
has_waypoint_to_building = link->data.entity == closest_building.entity;
|
||||
}
|
||||
|
||||
// NOTE: We have arrived at the waypoint
|
||||
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;
|
||||
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<FP_GameWaypoint> *link = FP_SentinelList_MakeBefore(&entity->waypoints, FP_SentinelList_Front(&entity->waypoints), game->play.chunk_pool);
|
||||
FP_GameWaypoint *waypoint = &link->data;
|
||||
waypoint->entity = closest_building.entity;
|
||||
waypoint->type = FP_GameWaypointType_Side;
|
||||
|
||||
|
||||
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->play.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)++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
while (entity->waypoints.size) {
|
||||
FP_SentinelListLink<FP_GameWaypoint> *waypoint_link = entity->waypoints.sentinel->next;
|
||||
FP_GameWaypoint const *waypoint = &waypoint_link->data;
|
||||
FP_GameEntity *waypoint_entity = FP_Game_GetEntity(game, waypoint_link->data.entity);
|
||||
if (FP_Game_IsNilEntity(waypoint_entity)) {
|
||||
FP_SentinelList_Erase(&entity->waypoints, waypoint_link, game->play.chunk_pool);
|
||||
continue;
|
||||
}
|
||||
|
||||
// NOTE: We found a waypoint that is valid to move towards
|
||||
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
|
||||
Dqn_f32 dist_to_waypoint_sq = Dqn_V2_LengthSq(entity_to_waypoint);
|
||||
|
||||
// NOTE: Calculate the approaching direction
|
||||
FP_GameDirection approach_dir = FP_GameDirection_Up;
|
||||
{
|
||||
Dqn_V2 dir_vectors[FP_GameDirection_Count] = {};
|
||||
dir_vectors[FP_GameDirection_Up] = Dqn_V2_InitNx2(+0, -1);
|
||||
dir_vectors[FP_GameDirection_Down] = Dqn_V2_InitNx2(+0, +1);
|
||||
dir_vectors[FP_GameDirection_Left] = Dqn_V2_InitNx2(-1, +0);
|
||||
dir_vectors[FP_GameDirection_Right] = Dqn_V2_InitNx2(+1, +0);
|
||||
|
||||
Dqn_V2 target_entity_pos = FP_Game_CalcEntityWorldPos(game, waypoint_entity->handle);
|
||||
Dqn_V2 entity_to_target = target_entity_pos - entity_pos;
|
||||
Dqn_V2 entity_to_target_norm = Dqn_V2_Normalise(entity_to_target);
|
||||
Dqn_f32 approach_dir_scalar_projection_onto_entity_to_waypoint_vector = -1.1f;
|
||||
DQN_FOR_UINDEX (dir_index, FP_GameDirection_Count) {
|
||||
Dqn_V2 attack_dir = dir_vectors[dir_index];
|
||||
Dqn_f32 scalar_projection = Dqn_V2_Dot(attack_dir, entity_to_target_norm);
|
||||
if (scalar_projection > approach_dir_scalar_projection_onto_entity_to_waypoint_vector) {
|
||||
approach_dir = DQN_CAST(FP_GameDirection)dir_index;
|
||||
approach_dir_scalar_projection_onto_entity_to_waypoint_vector = scalar_projection;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Dqn_f32 arrival_threshold = {};
|
||||
switch (waypoint->arrive) {
|
||||
case FP_GameWaypointArrive_Default: {
|
||||
Dqn_Rect waypoint_hit_box = FP_Game_CalcEntityWorldHitBox(game, waypoint_entity->handle);
|
||||
if (approach_dir == FP_GameDirection_Up || approach_dir == FP_GameDirection_Down)
|
||||
arrival_threshold = 10.f;
|
||||
else
|
||||
arrival_threshold = 10.f;
|
||||
} break;
|
||||
|
||||
case FP_GameWaypointArrive_WhenWithinEntitySize: {
|
||||
arrival_threshold = DQN_MAX(waypoint_entity->local_hit_box_size.w, waypoint_entity->local_hit_box_size.h) * waypoint_link->data.value;
|
||||
} break;
|
||||
}
|
||||
|
||||
// NOTE: We haven't arrived yet, calculate an acceleration vector to the waypoint
|
||||
if (dist_to_waypoint_sq > DQN_SQUARED(arrival_threshold)) {
|
||||
Dqn_V2 entity_to_waypoint_norm = Dqn_V2_Normalise(entity_to_waypoint);
|
||||
acceleration_meters_per_s = entity_to_waypoint_norm * (entity->base_acceleration_per_s.meters * 0.25f);
|
||||
break;
|
||||
}
|
||||
|
||||
// NOTE: We have arrived at the waypoint
|
||||
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;
|
||||
if (((waypoint->flags & FP_GameWaypointFlag_NonInterruptible) == 0) && (aggro || building_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)) {
|
||||
building->building_patron = entity->handle;
|
||||
|
||||
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));
|
||||
if (building->type == FP_EntityType_ClubTerry) {
|
||||
FP_Game_EntityTransitionState(game, building, FP_EntityClubTerryState_PartyTime);
|
||||
entity->local_pos = exit_pos; // TODO(doyle): Only works when parent world pos is 0,0
|
||||
} else if (building->type == FP_EntityType_AirportTerry) {
|
||||
FP_Game_EntityTransitionState(game, building, FP_EntityAirportTerryState_FlyPassenger);
|
||||
entity->local_pos = exit_pos; // TODO(doyle): Only works when parent world pos is 0,0
|
||||
} else {
|
||||
DQN_ASSERT(building->type == FP_EntityType_ChurchTerry);
|
||||
FP_Game_EntityTransitionState(game, building, FP_EntityChurchTerryState_ConvertPatron);
|
||||
}
|
||||
|
||||
entity->flags |= FP_GameEntityFlag_OccupiedInBuilding;
|
||||
can_attack = false;
|
||||
FP_SentinelList_Erase(&entity->waypoints, waypoint_link, game->play.chunk_pool);
|
||||
FP_SentinelList_Add(&entity->buildings_visited, game->play.chunk_pool, building->handle);
|
||||
}
|
||||
}
|
||||
|
||||
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 || 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)) {
|
||||
building->building_patron = entity->handle;
|
||||
|
||||
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));
|
||||
if (building->type == FP_EntityType_ClubTerry) {
|
||||
FP_Game_EntityTransitionState(game, building, FP_EntityClubTerryState_PartyTime);
|
||||
entity->local_pos = exit_pos; // TODO(doyle): Only works when parent world pos is 0,0
|
||||
} else if (building->type == FP_EntityType_AirportTerry) {
|
||||
FP_Game_EntityTransitionState(game, building, FP_EntityAirportTerryState_FlyPassenger);
|
||||
entity->local_pos = exit_pos; // TODO(doyle): Only works when parent world pos is 0,0
|
||||
if (can_attack) {
|
||||
switch (entity->type) {
|
||||
case FP_EntityType_Terry: /*FALLTHRU*/
|
||||
case FP_EntityType_Smoochie: /*FALLTHRU*/
|
||||
case FP_EntityType_Catfish: /*FALLTHRU*/
|
||||
case FP_EntityType_Clinger: {
|
||||
// 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) {
|
||||
FP_Game_EntityTransitionState(game, entity, FP_EntityTerryState_Attack);
|
||||
} else if (entity->type == FP_EntityType_Smoochie) {
|
||||
FP_Game_EntityTransitionState(game, entity, FP_EntitySmoochieState_Attack);
|
||||
} else if (entity->type == FP_EntityType_Catfish) {
|
||||
FP_Game_EntityTransitionState(game, entity, FP_EntityCatfishState_Attack);
|
||||
} else {
|
||||
DQN_ASSERT(building->type == FP_EntityType_ChurchTerry);
|
||||
FP_Game_EntityTransitionState(game, building, FP_EntityChurchTerryState_ConvertPatron);
|
||||
DQN_ASSERT(entity->type == FP_EntityType_Clinger);
|
||||
FP_Game_EntityTransitionState(game, entity, FP_EntityClingerState_Attack);
|
||||
}
|
||||
entity->direction = approach_dir;
|
||||
} break;
|
||||
|
||||
entity->flags |= FP_GameEntityFlag_OccupiedInBuilding;
|
||||
can_attack = false;
|
||||
FP_SentinelList_Erase(&entity->waypoints, waypoint_link, game->play.chunk_pool);
|
||||
FP_SentinelList_Add(&entity->buildings_visited, game->play.chunk_pool, building->handle);
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
if (can_attack) {
|
||||
Dqn_V2 attack_dir_vectors[FP_GameDirection_Count] = {};
|
||||
attack_dir_vectors[FP_GameDirection_Up] = Dqn_V2_InitNx2(+0, -1);
|
||||
attack_dir_vectors[FP_GameDirection_Down] = Dqn_V2_InitNx2(+0, +1);
|
||||
attack_dir_vectors[FP_GameDirection_Left] = Dqn_V2_InitNx2(-1, +0);
|
||||
attack_dir_vectors[FP_GameDirection_Right] = Dqn_V2_InitNx2(+1, +0);
|
||||
|
||||
Dqn_V2 defender_entity_pos = FP_Game_CalcEntityWorldPos(game, waypoint_entity->handle);
|
||||
Dqn_V2 entity_to_defender = defender_entity_pos - entity_pos;
|
||||
Dqn_V2 entity_to_defender_norm = Dqn_V2_Normalise(entity_to_defender);
|
||||
|
||||
FP_GameDirection best_attack_dir = FP_GameDirection_Up;
|
||||
Dqn_f32 best_attack_dir_scalar_projection_onto_entity_to_waypoint_vector = -1.1f;
|
||||
DQN_FOR_UINDEX (dir_index, FP_GameDirection_Count) {
|
||||
Dqn_V2 attack_dir = attack_dir_vectors[dir_index];
|
||||
Dqn_f32 scalar_projection = Dqn_V2_Dot(attack_dir, entity_to_defender_norm);
|
||||
if (scalar_projection > best_attack_dir_scalar_projection_onto_entity_to_waypoint_vector) {
|
||||
best_attack_dir = DQN_CAST(FP_GameDirection)dir_index;
|
||||
best_attack_dir_scalar_projection_onto_entity_to_waypoint_vector = scalar_projection;
|
||||
}
|
||||
}
|
||||
|
||||
switch (entity->type) {
|
||||
case FP_EntityType_Terry: /*FALLTHRU*/
|
||||
case FP_EntityType_Smoochie: /*FALLTHRU*/
|
||||
case FP_EntityType_Catfish: /*FALLTHRU*/
|
||||
case FP_EntityType_Clinger: {
|
||||
// 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) {
|
||||
FP_Game_EntityTransitionState(game, entity, FP_EntityTerryState_Attack);
|
||||
} else if (entity->type == FP_EntityType_Smoochie) {
|
||||
FP_Game_EntityTransitionState(game, entity, FP_EntitySmoochieState_Attack);
|
||||
} else if (entity->type == FP_EntityType_Catfish) {
|
||||
FP_Game_EntityTransitionState(game, entity, FP_EntityCatfishState_Attack);
|
||||
} else {
|
||||
DQN_ASSERT(entity->type == FP_EntityType_Clinger);
|
||||
FP_Game_EntityTransitionState(game, entity, FP_EntityClingerState_Attack);
|
||||
}
|
||||
entity->direction = best_attack_dir;
|
||||
} 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;
|
||||
}
|
||||
}
|
||||
|
||||
// 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->play.chunk_pool);
|
||||
}
|
||||
|
||||
// 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->play.chunk_pool);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1873,14 +1864,17 @@ void FP_Update(TELY_Platform *platform, FP_Game *game, TELY_PlatformInput *input
|
||||
TELY_Platform_InputScanCodeIsPressed(input, TELY_PlatformInputScanCode_J)) {
|
||||
if (placeable_building.type == FP_EntityType_ClubTerry) {
|
||||
FP_Entity_CreateClubTerry(game, placement_pos, "Club Terry");
|
||||
TELY_Audio_Play(audio, game->audio[FP_GameAudio_Club], 1.f);
|
||||
} else if (placeable_building.type == FP_EntityType_ChurchTerry) {
|
||||
FP_Entity_CreateChurchTerry(game, placement_pos, "Church Terry");
|
||||
TELY_Audio_Play(audio, game->audio[FP_GameAudio_Church], 1.f);
|
||||
} else if (placeable_building.type == FP_EntityType_AirportTerry) {
|
||||
FP_Entity_CreateAirportTerry(game, placement_pos, "Airport Terry");
|
||||
TELY_Audio_Play(audio, game->audio[FP_GameAudio_Plane], 1.f);
|
||||
} else {
|
||||
DQN_ASSERT(placeable_building.type == FP_EntityType_KennelTerry);
|
||||
FP_Entity_CreateKennelTerry(game, placement_pos, "Kennel Terry");
|
||||
TELY_Audio_Play(audio, game->audio[FP_GameAudio_Dog], 1.f);
|
||||
}
|
||||
|
||||
(*inventory_count)--;
|
||||
@@ -1943,10 +1937,12 @@ void FP_Update(TELY_Platform *platform, FP_Game *game, TELY_PlatformInput *input
|
||||
entity->terry_mobile_data_plan_cap);
|
||||
|
||||
// NOTE: Recover hp & stamina
|
||||
entity->stamina = DQN_MIN(entity->stamina + 1, entity->stamina_cap);
|
||||
if (game->play.update_counter % 4 == 0) {
|
||||
entity->stamina = DQN_MIN(entity->stamina + 1, entity->stamina_cap);
|
||||
}
|
||||
|
||||
if (entity->flags & FP_GameEntityFlag_RecoversHP) {
|
||||
if (game->play.update_counter % 12 == 0) {
|
||||
if (game->play.update_counter % entity->hp_recover_every_n_ticks == 0) {
|
||||
entity->hp = DQN_MIN(entity->hp + 1, entity->hp_cap);
|
||||
}
|
||||
}
|
||||
@@ -2102,6 +2098,15 @@ void FP_Update(TELY_Platform *platform, FP_Game *game, TELY_PlatformInput *input
|
||||
game->play.state = FP_GameState_WinGame;
|
||||
}
|
||||
|
||||
{
|
||||
FP_GameEntity *heart = FP_Game_GetEntity(game, game->play.heart);
|
||||
FP_GameEntity *player = FP_Game_GetEntity(game, game->play.player);
|
||||
if (heart->hp <= 0) {
|
||||
player->hp = 0;
|
||||
game->play.state = FP_GameState_LoseGame;
|
||||
}
|
||||
}
|
||||
|
||||
if (!FP_Game_IsNilEntityHandle(game, game->play.clicked_entity)) {
|
||||
TELY_AssetSpriteAnimation *sprite_anim = TELY_Asset_GetSpriteAnimation(&game->atlas_sprite_sheet, g_anim_names.map);
|
||||
Dqn_Rect sprite_rect = game->atlas_sprite_sheet.rects.data[sprite_anim->index];
|
||||
@@ -2303,7 +2308,7 @@ void FP_Render(FP_Game *game, TELY_Platform *platform, TELY_Renderer *renderer,
|
||||
TELY_COLOUR_WHITE_V4);
|
||||
}
|
||||
|
||||
if (entity->handle != game->play.player && entity->hp != entity->hp_cap && entity->hp) {
|
||||
if (entity->handle != game->play.player && entity->handle != game->play.heart && entity->hp != entity->hp_cap && entity->hp) {
|
||||
Dqn_f32 bar_height = 12.f;
|
||||
TELY_AssetSpriteAnimation *anim = TELY_Asset_GetSpriteAnimation(&game->atlas_sprite_sheet, g_anim_names.icon_health);
|
||||
Dqn_Rect icon_tex_rect = game->atlas_sprite_sheet.rects.data[anim->index];
|
||||
@@ -2422,7 +2427,7 @@ void FP_Render(FP_Game *game, TELY_Platform *platform, TELY_Renderer *renderer,
|
||||
}
|
||||
}
|
||||
|
||||
if (game->play.state == FP_GameState_IntroScreen || game->play.state == FP_GameState_WinGame) {
|
||||
if (game->play.state == FP_GameState_IntroScreen || game->play.state == FP_GameState_WinGame || game->play.state == FP_GameState_LoseGame) {
|
||||
TELY_Render_PushTransform(renderer, Dqn_M2x3_Identity());
|
||||
DQN_DEFER { TELY_Render_PopTransform(renderer); };
|
||||
TELY_Render_RectColourV4(
|
||||
@@ -2449,6 +2454,20 @@ void FP_Render(FP_Game *game, TELY_Platform *platform, TELY_Renderer *renderer,
|
||||
|
||||
if (TELY_Platform_InputScanCodeIsPressed(input, TELY_PlatformInputScanCode_Return))
|
||||
game->play.state = FP_GameState_Play;
|
||||
} else if (game->play.state == FP_GameState_LoseGame) {
|
||||
TELY_Render_PushFont(renderer, game->inter_regular_font_large);
|
||||
TELY_Render_TextF(renderer, draw_p, Dqn_V2_Zero, "Terry's heart has been crushed"); draw_p.y += TELY_Render_FontHeight(renderer, assets);
|
||||
TELY_Render_TextF(renderer, draw_p, Dqn_V2_Zero, "from the hoard of monsters"); draw_p.y += TELY_Render_FontHeight(renderer, assets);
|
||||
TELY_Render_PopFont(renderer);
|
||||
|
||||
TELY_Render_PushFont(renderer, game->inter_regular_font);
|
||||
TELY_Render_TextF(renderer, draw_p, Dqn_V2_Zero, "Sayounara amigo"); draw_p.y += TELY_Render_FontHeight(renderer, assets);
|
||||
TELY_Render_TextF(renderer, draw_p, Dqn_V2_Zero, "Press enter to restart"); draw_p.y += TELY_Render_FontHeight(renderer, assets);
|
||||
TELY_Render_PopFont(renderer);
|
||||
TELY_Render_PopColourV4(renderer);
|
||||
|
||||
if (TELY_Platform_InputScanCodeIsPressed(input, TELY_PlatformInputScanCode_Return))
|
||||
FP_PlayReset(game, platform);
|
||||
} else {
|
||||
TELY_Render_PushFont(renderer, game->inter_regular_font_large);
|
||||
TELY_Render_TextF(renderer, draw_p, Dqn_V2_Zero, "Terry has been saved"); draw_p.y += TELY_Render_FontHeight(renderer, assets);
|
||||
@@ -2470,6 +2489,7 @@ void FP_Render(FP_Game *game, TELY_Platform *platform, TELY_Renderer *renderer,
|
||||
FP_GameEntity *player = FP_Game_GetEntity(game, game->play.player);
|
||||
Dqn_V2 player_pos = FP_Game_CalcEntityWorldPos(game, game->play.player);
|
||||
{
|
||||
static bool sound_played_flags[4] = {false, false, false, false};
|
||||
FP_GameInventory *invent = &player->inventory;
|
||||
struct FP_MerchantToMenuMapping {
|
||||
FP_GameEntityHandle merchant;
|
||||
@@ -2479,11 +2499,13 @@ void FP_Render(FP_Game *game, TELY_Platform *platform, TELY_Renderer *renderer,
|
||||
uint8_t *inventory_count;
|
||||
uint32_t *building_base_price;
|
||||
uint32_t *upgrade_base_price;
|
||||
FP_GameAudio audio_type;
|
||||
bool *sound_played;
|
||||
} merchants[] = {
|
||||
{game->play.merchant_terry, g_anim_names.merchant_terry_menu, g_anim_names.club_terry_dark, Dqn_V2_InitNx2(0.015f, +0.04f), &invent->clubs, &invent->clubs_base_price, &invent->health_base_price},
|
||||
{game->play.merchant_graveyard, g_anim_names.merchant_graveyard_menu, g_anim_names.church_terry_dark, Dqn_V2_InitNx2(0.04f, -0.15f), &invent->churchs, &invent->churchs_base_price, &invent->stamina_base_price},
|
||||
{game->play.merchant_gym, g_anim_names.merchant_gym_menu, g_anim_names.kennel_terry, Dqn_V2_InitNx2(0, +0), &invent->kennels, &invent->kennels_base_price, &invent->attack_base_price},
|
||||
{game->play.merchant_phone_company, g_anim_names.merchant_phone_company_menu, g_anim_names.airport_terry, Dqn_V2_InitNx2(0, -0.1f), &invent->airports, &invent->airports_base_price, &invent->mobile_plan_base_price},
|
||||
{game->play.merchant_terry, g_anim_names.merchant_terry_menu, g_anim_names.club_terry_dark, Dqn_V2_InitNx2(0.015f, +0.04f), &invent->clubs, &invent->clubs_base_price, &invent->health_base_price, FP_GameAudio_MerchantTerry, &sound_played_flags[0]},
|
||||
{game->play.merchant_graveyard, g_anim_names.merchant_graveyard_menu, g_anim_names.church_terry_dark, Dqn_V2_InitNx2(0.04f, -0.15f), &invent->churchs, &invent->churchs_base_price, &invent->stamina_base_price, FP_GameAudio_MerchantGhost, &sound_played_flags[1]},
|
||||
{game->play.merchant_gym, g_anim_names.merchant_gym_menu, g_anim_names.kennel_terry, Dqn_V2_InitNx2(0, +0), &invent->kennels, &invent->kennels_base_price, &invent->attack_base_price, FP_GameAudio_MerchantGym, &sound_played_flags[2]},
|
||||
{game->play.merchant_phone_company, g_anim_names.merchant_phone_company_menu, g_anim_names.airport_terry, Dqn_V2_InitNx2(0, -0.1f), &invent->airports, &invent->airports_base_price, &invent->mobile_plan_base_price, FP_GameAudio_MerchantPhone, &sound_played_flags[3]},
|
||||
};
|
||||
|
||||
bool activated_merchant = false;
|
||||
@@ -2492,8 +2514,10 @@ void FP_Render(FP_Game *game, TELY_Platform *platform, TELY_Renderer *renderer,
|
||||
Dqn_V2 world_pos = FP_Game_CalcEntityWorldPos(game, merchant_handle);
|
||||
Dqn_f32 dist_squared = Dqn_V2_LengthSq_V2x2(world_pos, player_pos);
|
||||
|
||||
if (dist_squared > DQN_SQUARED(FP_Game_MetersToPixelsNx1(game->play, 4)))
|
||||
if (dist_squared > DQN_SQUARED(FP_Game_MetersToPixelsNx1(game->play, 4))) {
|
||||
*mapping.sound_played = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
// NOTE: Render animated merchant menu =============================
|
||||
activated_merchant = true;
|
||||
@@ -2524,6 +2548,11 @@ void FP_Render(FP_Game *game, TELY_Platform *platform, TELY_Renderer *renderer,
|
||||
Dqn_V2_Zero /*rotate origin*/,
|
||||
0.f /*rotation*/,
|
||||
TELY_COLOUR_WHITE_V4);
|
||||
|
||||
if (activated_merchant && !*mapping.sound_played) {
|
||||
TELY_Audio_Play(audio, game->audio[mapping.audio_type], 1.f);
|
||||
*mapping.sound_played = true;
|
||||
}
|
||||
}
|
||||
|
||||
TELY_Render_PushColourV4(renderer, TELY_COLOUR_WHITE_V4);
|
||||
@@ -3029,6 +3058,53 @@ void FP_Render(FP_Game *game, TELY_Platform *platform, TELY_Renderer *renderer,
|
||||
}
|
||||
}
|
||||
|
||||
// NOTE: Render the heart health
|
||||
{
|
||||
TELY_Render_PushTransform(renderer, Dqn_M2x3_Identity());
|
||||
DQN_DEFER { TELY_Render_PopTransform(renderer); };
|
||||
|
||||
Dqn_f32 font_height = TELY_Render_FontHeight(renderer, assets);
|
||||
Dqn_f32 bar_height = font_height * 1.25f;
|
||||
{
|
||||
TELY_AssetSpriteAnimation *anim = TELY_Asset_GetSpriteAnimation(&game->atlas_sprite_sheet, g_anim_names.heart);
|
||||
FP_GameEntity *heart = FP_Game_GetEntity(game, game->play.heart);
|
||||
|
||||
Dqn_f32 max_width = platform->core.window_size.x * .5f;
|
||||
|
||||
Dqn_V2 draw_p = Dqn_V2_InitNx2(platform->core.window_size.x * .25f, player_avatar_rect.pos.y + bar_height);
|
||||
Dqn_f32 health_t = heart->hp / DQN_CAST(Dqn_f32)heart->hp_cap;
|
||||
Dqn_Rect health_rect = Dqn_Rect_InitNx4(draw_p.x, draw_p.y, max_width, bar_height);
|
||||
Dqn_Rect curr_health_rect = Dqn_Rect_InitNx4(draw_p.x, draw_p.y, max_width * health_t, bar_height);
|
||||
|
||||
TELY_Render_RectColourV4(renderer, curr_health_rect, TELY_RenderShapeMode_Fill, TELY_COLOUR_RED_TOMATO_V4);
|
||||
|
||||
TELY_RenderCommandRect *cmd = TELY_Render_RectColourV4(
|
||||
renderer,
|
||||
health_rect,
|
||||
TELY_RenderShapeMode_Line,
|
||||
TELY_COLOUR_BLACK_V4);
|
||||
cmd->thickness = 4.f;
|
||||
|
||||
// NOTE: Draw the heart icon
|
||||
Dqn_Rect icon_tex_rect = game->atlas_sprite_sheet.rects.data[anim->index];
|
||||
Dqn_Rect heart_icon_rect = {};
|
||||
heart_icon_rect.size = icon_tex_rect.size * .4f;
|
||||
heart_icon_rect.pos = Dqn_V2_InitNx2(draw_p.x - (heart_icon_rect.size.w * .7f), draw_p.y - (heart_icon_rect.size.y * .4f));
|
||||
|
||||
TELY_Render_TextureColourV4(renderer,
|
||||
game->atlas_sprite_sheet.tex_handle,
|
||||
icon_tex_rect,
|
||||
heart_icon_rect,
|
||||
Dqn_V2_Zero /*rotate origin*/,
|
||||
0.f /*rotation*/,
|
||||
TELY_COLOUR_WHITE_V4);
|
||||
|
||||
TELY_Render_PushFont(renderer, game->talkco_font_large);
|
||||
DQN_DEFER { TELY_Render_PopFont(renderer); };
|
||||
TELY_Render_TextF(renderer, Dqn_Rect_InterpolatedPoint(heart_icon_rect, Dqn_V2_InitNx2(1.f, 0.65f)), Dqn_V2_Zero, "Terry's Heart");
|
||||
}
|
||||
}
|
||||
|
||||
// NOTE: Add scanlines into the game for A E S T H E T I C S
|
||||
Dqn_V2 screen_size = Dqn_V2_InitNx2(platform->core.window_size.w, platform->core.window_size.h);
|
||||
Dqn_f32 scanline_gap = 4.0f;
|
||||
|
||||
@@ -637,7 +637,13 @@ static FP_GameEntityHandle FP_Entity_CreateHeart(FP_Game *game, Dqn_V2 pos, DQN_
|
||||
FP_Game_EntityActionReset(game, result, duration_ms, render_data.sprite);
|
||||
|
||||
FP_Entity_AddDebugEditorFlags(game, result);
|
||||
entity->flags |= FP_GameEntityFlag_NonTraversable;
|
||||
entity->flags |= FP_GameEntityFlag_NonTraversable;
|
||||
entity->flags |= FP_GameEntityFlag_Attackable;
|
||||
entity->flags |= FP_GameEntityFlag_RecoversHP;
|
||||
entity->hp_cap = FP_DEFAULT_DAMAGE * 16;
|
||||
entity->hp = entity->hp_cap;
|
||||
entity->faction = FP_GameEntityFaction_Friendly;
|
||||
entity->hp_recover_every_n_ticks *= 4;
|
||||
|
||||
TELY_AssetSpriteAnimation *sprite_anim = TELY_Asset_GetSpriteAnimation(&game->atlas_sprite_sheet, g_anim_names.heart);
|
||||
Dqn_Rect sprite_rect = game->atlas_sprite_sheet.rects.data[sprite_anim->index];
|
||||
|
||||
@@ -234,6 +234,7 @@ static FP_GameEntity *FP_Game_MakeEntityPointerFV(FP_Game *game, DQN_FMT_STRING_
|
||||
result->inventory.kennels_base_price = 50;
|
||||
result->inventory.clubs_base_price = 50;
|
||||
result->base_attack = FP_DEFAULT_DAMAGE;
|
||||
result->hp_recover_every_n_ticks = 12;
|
||||
|
||||
result->inventory.stamina_base_price = 10;
|
||||
result->inventory.health_base_price = 10;
|
||||
@@ -742,6 +743,7 @@ static Dqn_V2 FP_Game_CalcWaypointWorldPos(FP_Game *game, FP_GameEntityHandle sr
|
||||
}
|
||||
}
|
||||
|
||||
#if 0
|
||||
Dqn_V2 entity_pos = FP_Game_CalcEntityWorldPos(game, waypoint_entity->handle);
|
||||
Dqn_V2 src_pos = FP_Game_CalcEntityWorldPos(game, src_entity);
|
||||
Dqn_f32 curr_dist_to_entity = Dqn_V2_LengthSq_V2x2(entity_pos, src_pos);
|
||||
@@ -750,6 +752,7 @@ static Dqn_V2 FP_Game_CalcWaypointWorldPos(FP_Game *game, FP_GameEntityHandle sr
|
||||
// we assume we're at the entity already.
|
||||
result = FP_Game_CalcEntityWorldPos(game, src_entity);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
} break;
|
||||
|
||||
|
||||
+17
-8
@@ -17,14 +17,13 @@ enum FP_GameEntityFlag
|
||||
FP_GameEntityFlag_Attackable = 1 << 9,
|
||||
FP_GameEntityFlag_RespondsToBuildings = 1 << 10,
|
||||
FP_GameEntityFlag_OccupiedInBuilding = 1 << 11,
|
||||
FP_GameEntityFlag_PointOfInterestHeart = 1 << 12,
|
||||
FP_GameEntityFlag_CameraTracking = 1 << 13,
|
||||
FP_GameEntityFlag_BuildZone = 1 << 14,
|
||||
FP_GameEntityFlag_TTL = 1 << 15,
|
||||
FP_GameEntityFlag_Friendly = 1 << 16,
|
||||
FP_GameEntityFlag_Foe = 1 << 17,
|
||||
FP_GameEntityFlag_NoClip = 1 << 18,
|
||||
FP_GameEntityFlag_RecoversHP = 1 << 19,
|
||||
FP_GameEntityFlag_CameraTracking = 1 << 12,
|
||||
FP_GameEntityFlag_BuildZone = 1 << 13,
|
||||
FP_GameEntityFlag_TTL = 1 << 14,
|
||||
FP_GameEntityFlag_Friendly = 1 << 15,
|
||||
FP_GameEntityFlag_Foe = 1 << 16,
|
||||
FP_GameEntityFlag_NoClip = 1 << 17,
|
||||
FP_GameEntityFlag_RecoversHP = 1 << 18,
|
||||
};
|
||||
|
||||
enum FP_GameShapeType
|
||||
@@ -228,6 +227,7 @@ struct FP_GameEntity
|
||||
uint32_t hp_cap;
|
||||
uint32_t stamina;
|
||||
uint32_t stamina_cap;
|
||||
uint32_t hp_recover_every_n_ticks;
|
||||
uint32_t base_attack;
|
||||
|
||||
bool converted_faction;
|
||||
@@ -265,6 +265,13 @@ enum FP_GameAudio
|
||||
FP_GameAudio_Woosh,
|
||||
FP_GameAudio_Ching,
|
||||
FP_GameAudio_Church,
|
||||
FP_GameAudio_Plane,
|
||||
FP_GameAudio_Club,
|
||||
FP_GameAudio_Dog,
|
||||
FP_GameAudio_MerchantTerry,
|
||||
FP_GameAudio_MerchantGhost,
|
||||
FP_GameAudio_MerchantGym,
|
||||
FP_GameAudio_MerchantPhone,
|
||||
FP_GameAudio_Count,
|
||||
};
|
||||
|
||||
@@ -280,6 +287,7 @@ enum FP_GameState
|
||||
FP_GameState_IntroScreen,
|
||||
FP_GameState_Play,
|
||||
FP_GameState_WinGame,
|
||||
FP_GameState_LoseGame,
|
||||
};
|
||||
|
||||
struct FP_GamePlay
|
||||
@@ -299,6 +307,7 @@ struct FP_GamePlay
|
||||
FP_GameRenderSprite player_merchant_menu;
|
||||
uint64_t player_trigger_purchase_upgrade_timestamp;
|
||||
uint64_t player_trigger_purchase_building_timestamp;
|
||||
FP_GameEntityHandle heart;
|
||||
|
||||
FP_GameEntityHandle merchant_terry;
|
||||
FP_GameEntityHandle merchant_graveyard;
|
||||
|
||||
Reference in New Issue
Block a user