2 Commits

Author SHA1 Message Date
doylet 25736461fb fp: Add game over sequence 2023-10-08 19:05:56 +11:00
Jojangles a651bc7e14 Rebase changes 2023-10-08 19:00:13 +11:00
15 changed files with 406 additions and 303 deletions
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+1 -1
+149 -73
View File
@@ -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,17 +459,18 @@ 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");
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);
}
@@ -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,7 +1522,6 @@ 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;
@@ -1535,7 +1548,15 @@ void FP_Update(TELY_Platform *platform, FP_Game *game, TELY_PlatformInput *input
}
Dqn_f32 aggro_dist_threshold = FP_Game_MetersToPixelsNx1(game->play, 4.f);
if (closest_defender.dist_squared < DQN_SQUARED(aggro_dist_threshold)) {
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); ) {
@@ -1551,15 +1572,6 @@ void FP_Update(TELY_Platform *platform, FP_Game *game, TELY_PlatformInput *input
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);
}
}
}
}
}
@@ -1643,29 +1655,6 @@ void FP_Update(TELY_Platform *platform, FP_Game *game, TELY_PlatformInput *input
}
}
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))) {
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;
}
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;
@@ -1682,11 +1671,37 @@ void FP_Update(TELY_Platform *platform, FP_Game *game, TELY_PlatformInput *input
// 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);
arrival_threshold = waypoint_hit_box.size.w * .5f;
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: {
@@ -1712,9 +1727,7 @@ void FP_Update(TELY_Platform *platform, FP_Game *game, TELY_PlatformInput *input
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)) {
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;
@@ -1742,27 +1755,6 @@ void FP_Update(TELY_Platform *platform, FP_Game *game, TELY_PlatformInput *input
}
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*/
@@ -1780,7 +1772,7 @@ void FP_Update(TELY_Platform *platform, FP_Game *game, TELY_PlatformInput *input
DQN_ASSERT(entity->type == FP_EntityType_Clinger);
FP_Game_EntityTransitionState(game, entity, FP_EntityClingerState_Attack);
}
entity->direction = best_attack_dir;
entity->direction = approach_dir;
} break;
case FP_EntityType_Nil: break;
@@ -1805,7 +1797,6 @@ void FP_Update(TELY_Platform *platform, FP_Game *game, TELY_PlatformInput *input
FP_SentinelList_Erase(&entity->waypoints, waypoint_link, game->play.chunk_pool);
}
}
}
// NOTE: Move entity by mouse ==============================================================
if (game->play.active_entity == entity->handle && entity->flags & FP_GameEntityFlag_MoveByMouse) {
@@ -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
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;
+6
View File
@@ -638,6 +638,12 @@ static FP_GameEntityHandle FP_Entity_CreateHeart(FP_Game *game, Dqn_V2 pos, DQN_
FP_Entity_AddDebugEditorFlags(game, result);
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];
+3
View File
@@ -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
View File
@@ -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;