From 158fcb12fefdf400c25395c966af0bde81d06663 Mon Sep 17 00:00:00 2001 From: doyle Date: Fri, 29 Sep 2023 21:42:27 +1000 Subject: [PATCH] fp: Add mobbing behaviour to surround club terry --- feely_pona.cpp | 108 +++++++++++++---------------------- feely_pona.h | 33 +++++++++++ feely_pona_entity_create.cpp | 24 ++++++-- feely_pona_game.cpp | 35 ++++++++++-- feely_pona_game.h | 13 ++++- 5 files changed, 133 insertions(+), 80 deletions(-) diff --git a/feely_pona.cpp b/feely_pona.cpp index f69f401..11ffdcf 100644 --- a/feely_pona.cpp +++ b/feely_pona.cpp @@ -4,38 +4,6 @@ #endif Dqn_f32 const PHYSICS_STEP = 1 / 60.f; -struct FP_GlobalAnimations -{ - Dqn_String8 terry_walk_idle = DQN_STRING8("terry_walk_idle"); - Dqn_String8 terry_walk_up = DQN_STRING8("terry_walk_up"); - Dqn_String8 terry_walk_down = DQN_STRING8("terry_walk_down"); - Dqn_String8 terry_walk_left = DQN_STRING8("terry_walk_left"); - Dqn_String8 terry_walk_right = DQN_STRING8("terry_walk_right"); - Dqn_String8 terry_attack_up = DQN_STRING8("terry_attack_up"); - Dqn_String8 terry_attack_side = DQN_STRING8("terry_attack_side"); - Dqn_String8 terry_attack_down = DQN_STRING8("terry_attack_down"); - Dqn_String8 terry_merchant = DQN_STRING8("terry_merchant"); - - Dqn_String8 smoochie_walk_up = DQN_STRING8("smoochie_walk_up"); - Dqn_String8 smoochie_walk_down = DQN_STRING8("smoochie_walk_down"); - Dqn_String8 smoochie_walk_left = DQN_STRING8("smoochie_walk_left"); - Dqn_String8 smoochie_walk_right = DQN_STRING8("smoochie_walk_right"); - Dqn_String8 smoochie_attack_down = DQN_STRING8("smoochie_attack_down"); - Dqn_String8 smoochie_hurt_side = DQN_STRING8("smoochie_hurt_side"); - Dqn_String8 smoochie_attack_heart = DQN_STRING8("smoochie_attack_heart"); - Dqn_String8 smoochie_death = DQN_STRING8("smoochie_death"); - - Dqn_String8 clinger_attack_down = DQN_STRING8("clinger_attack_down"); - Dqn_String8 clinger_attack_side = DQN_STRING8("clinger_attack_side"); - Dqn_String8 clinger_attack_up = DQN_STRING8("clinger_attack_up"); - Dqn_String8 clinger_death = DQN_STRING8("clinger_death"); - Dqn_String8 clinger_walk_up = DQN_STRING8("clinger_walk_up"); - Dqn_String8 clinger_walk_down = DQN_STRING8("clinger_walk_down"); - - Dqn_String8 club_terry_alive = DQN_STRING8("club_terry_alive"); - Dqn_String8 club_terry_dark = DQN_STRING8("club_terry_dark"); -} -g_anim_names; TELY_AssetSpriteSheet FP_LoadSpriteSheetFromSpec(TELY_Platform *platform, TELY_Assets *assets, Dqn_Arena *arena, Dqn_String8 sheet_name) { @@ -964,8 +932,10 @@ void FP_Update(TELY_Platform *platform, FP_Game *game, TELY_PlatformInput *input move_entity = *state == FP_EntityClingerState_Run || *state == FP_EntityClingerState_Idle; } break; - case FP_EntityType_Merchant: break; - case FP_EntityType_Nil: break; + case FP_EntityType_Merchant: break; + case FP_EntityType_Nil: break; + case FP_EntityType_ClubTerry: break; + case FP_EntityType_Count: break; } if (move_entity) { @@ -988,30 +958,14 @@ void FP_Update(TELY_Platform *platform, FP_Game *game, TELY_PlatformInput *input Dqn_V2 entity_pos = FP_Game_CalcEntityWorldPos(game, entity->handle); if (entity->flags & FP_GameEntityFlag_AggrosWhenNearTerry) { - Dqn_f32 closest_terry_dist = DQN_F32_MAX; - Dqn_V2 closest_terry_pos = Dqn_V2_InitNx1(DQN_F32_MAX); - FP_GameEntity *closest_terry = nullptr; - for (FP_GameEntityIterator terry_it = {}; FP_Game_DFSPostOrderWalkEntityTree(game, &terry_it, game->root_entity); ) { - FP_GameEntity *terry = terry_it.entity; - if (terry->type != FP_EntityType_Terry) - continue; - - Dqn_V2 terry_pos = FP_Game_CalcEntityWorldPos(game, terry->handle); - Dqn_f32 dist_to_terry = Dqn_V2_LengthSq_V2x2(terry_pos, entity_pos); - if (dist_to_terry < closest_terry_dist) { - closest_terry_pos = terry_pos; - closest_terry_dist = dist_to_terry; - closest_terry = terry; - } - } - - if (closest_terry_dist < DQN_SQUARED(FP_Game_MetersToPixelsNx1(game, 4.f))) { + FP_GameFindClosestEntityResult closest_result = FP_Game_FindClosestEntityWithType(game, entity->handle, FP_EntityType_Terry); + if (closest_result.dist_squared < DQN_SQUARED(FP_Game_MetersToPixelsNx1(game, 4.f))) { FP_SentinelListLink *first_waypoint = FP_SentinelList_Front(&entity->waypoints); - if (first_waypoint->data.entity != closest_terry->handle) { - + if (first_waypoint->data.entity != closest_result.entity) { + FP_GameEntity *terry = FP_Game_GetEntity(game, closest_result.entity); FP_GameDirection aggro_direction = FP_GameDirection_Count; DQN_FOR_UINDEX(dir_index, FP_GameDirection_Count) { - FP_GameEntityHandle slot_entity_handle = closest_terry->aggro_slot[dir_index]; + FP_GameEntityHandle slot_entity_handle = terry->aggro_slot[dir_index]; FP_GameEntity *slot_entity = FP_Game_GetEntity(game, slot_entity_handle); if (FP_Game_IsNilEntity(slot_entity)) { aggro_direction = DQN_CAST(FP_GameDirection)dir_index; @@ -1021,17 +975,33 @@ void FP_Update(TELY_Platform *platform, FP_Game *game, TELY_PlatformInput *input FP_SentinelListLink *link = FP_SentinelList_MakeBefore(&entity->waypoints, first_waypoint, game->chunk_pool); FP_GameWaypoint *waypoint = &link->data; - waypoint->entity = closest_terry->handle; + waypoint->entity = terry->handle; if (aggro_direction != FP_GameDirection_Count) { - waypoint->type = FP_GameWaypointType_Side; - waypoint->type_direction = aggro_direction; - closest_terry->aggro_slot[aggro_direction] = entity->handle; + waypoint->type = FP_GameWaypointType_Side; + waypoint->type_direction = aggro_direction; + terry->aggro_slot[aggro_direction] = entity->handle; } } } } + if (entity->flags & FP_GameEntityFlag_RespondsToClubTerry) { + FP_GameFindClosestEntityResult closest_result = FP_Game_FindClosestEntityWithType(game, entity->handle, FP_EntityType_ClubTerry); + if (closest_result.dist_squared < DQN_SQUARED(FP_Game_MetersToPixelsNx1(game, 4.f))) { + FP_SentinelListLink *first_waypoint = FP_SentinelList_Front(&entity->waypoints); + if (first_waypoint->data.entity != closest_result.entity) { + FP_GameEntity *club_terry = FP_Game_GetEntity(game, closest_result.entity); + + FP_SentinelListLink *link = FP_SentinelList_MakeBefore(&entity->waypoints, first_waypoint, game->chunk_pool); + FP_GameWaypoint *waypoint = &link->data; + waypoint->entity = club_terry->handle; + waypoint->type = FP_GameWaypointType_Side; + waypoint->type_direction = FP_GameDirection_Down; + } + } + } + while (entity->waypoints.size) { FP_SentinelListLink *waypoint_link = entity->waypoints.sentinel->next; FP_GameWaypoint const *waypoint = &waypoint_link->data; @@ -1051,7 +1021,7 @@ void FP_Update(TELY_Platform *platform, FP_Game *game, TELY_PlatformInput *input Dqn_f32 arrival_threshold = {}; switch (waypoint->arrive) { case FP_GameWaypointArrive_Default: { - arrival_threshold = FP_Game_MetersToPixelsNx1(game, 1.f); + arrival_threshold = FP_Game_MetersToPixelsNx1(game, 0.5f); } break; case FP_GameWaypointArrive_WhenWithinEntitySize: { @@ -1067,14 +1037,11 @@ void FP_Update(TELY_Platform *platform, FP_Game *game, TELY_PlatformInput *input } // NOTE: We have arrived at the waypoint - if ((entity->flags & FP_GameEntityFlag_AggrosWhenNearTerry) && waypoint_entity->type == FP_EntityType_Terry) { - // NOTE: We had a waypoint to move to Terry because he has - // drawn our aggro and we've arrived at Terry - - bool can_attack = !entity->is_dying; - + bool aggro_on_terry = (entity->flags & FP_GameEntityFlag_AggrosWhenNearTerry) && waypoint_entity->type == FP_EntityType_Terry; + bool club_terry_response = (entity->flags & FP_GameEntityFlag_RespondsToClubTerry) && waypoint_entity->type == FP_EntityType_ClubTerry; + if (aggro_on_terry || club_terry_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 (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); @@ -1117,7 +1084,8 @@ void FP_Update(TELY_Platform *platform, FP_Game *game, TELY_PlatformInput *input entity->direction = best_attack_dir; } break; - case FP_EntityType_Count: DQN_INVALID_CODE_PATH; break; + case FP_EntityType_Count: DQN_INVALID_CODE_PATH; break; + case FP_EntityType_ClubTerry: break; } } @@ -1195,6 +1163,7 @@ void FP_Update(TELY_Platform *platform, FP_Game *game, TELY_PlatformInput *input FP_GameEntity *mob = FP_Game_GetEntity(game, link->data); mob->waypoints = FP_SentinelList_Init(game->chunk_pool); mob->flags |= FP_GameEntityFlag_AggrosWhenNearTerry; + mob->flags |= FP_GameEntityFlag_RespondsToClubTerry; for (FP_GameEntity *waypoint_entity = entity->first_child; waypoint_entity; waypoint_entity = waypoint_entity->next) { if ((waypoint_entity->flags & FP_GameEntityFlag_MobSpawnerWaypoint) == 0) @@ -1245,7 +1214,8 @@ void FP_Update(TELY_Platform *platform, FP_Game *game, TELY_PlatformInput *input case FP_EntityType_Nil: /*FALLTRHU*/ case FP_EntityType_Terry: /*FALLTRHU*/ case FP_EntityType_Merchant: /*FALLTRHU*/ - case FP_EntityType_Count: break; + case FP_EntityType_Count: break; + case FP_EntityType_ClubTerry: break; } if (!permit_attack) diff --git a/feely_pona.h b/feely_pona.h index 6720e68..04179fb 100644 --- a/feely_pona.h +++ b/feely_pona.h @@ -21,3 +21,36 @@ struct FP_Meters Dqn_f32 meters; }; +struct FP_GlobalAnimations +{ + Dqn_String8 terry_walk_idle = DQN_STRING8("terry_walk_idle"); + Dqn_String8 terry_walk_up = DQN_STRING8("terry_walk_up"); + Dqn_String8 terry_walk_down = DQN_STRING8("terry_walk_down"); + Dqn_String8 terry_walk_left = DQN_STRING8("terry_walk_left"); + Dqn_String8 terry_walk_right = DQN_STRING8("terry_walk_right"); + Dqn_String8 terry_attack_up = DQN_STRING8("terry_attack_up"); + Dqn_String8 terry_attack_side = DQN_STRING8("terry_attack_side"); + Dqn_String8 terry_attack_down = DQN_STRING8("terry_attack_down"); + Dqn_String8 terry_merchant = DQN_STRING8("terry_merchant"); + + Dqn_String8 smoochie_walk_up = DQN_STRING8("smoochie_walk_up"); + Dqn_String8 smoochie_walk_down = DQN_STRING8("smoochie_walk_down"); + Dqn_String8 smoochie_walk_left = DQN_STRING8("smoochie_walk_left"); + Dqn_String8 smoochie_walk_right = DQN_STRING8("smoochie_walk_right"); + Dqn_String8 smoochie_attack_down = DQN_STRING8("smoochie_attack_down"); + Dqn_String8 smoochie_hurt_side = DQN_STRING8("smoochie_hurt_side"); + Dqn_String8 smoochie_attack_heart = DQN_STRING8("smoochie_attack_heart"); + Dqn_String8 smoochie_death = DQN_STRING8("smoochie_death"); + + Dqn_String8 clinger_attack_down = DQN_STRING8("clinger_attack_down"); + Dqn_String8 clinger_attack_side = DQN_STRING8("clinger_attack_side"); + Dqn_String8 clinger_attack_up = DQN_STRING8("clinger_attack_up"); + Dqn_String8 clinger_death = DQN_STRING8("clinger_death"); + Dqn_String8 clinger_walk_up = DQN_STRING8("clinger_walk_up"); + Dqn_String8 clinger_walk_down = DQN_STRING8("clinger_walk_down"); + + Dqn_String8 club_terry_alive = DQN_STRING8("club_terry_alive"); + Dqn_String8 club_terry_dark = DQN_STRING8("club_terry_dark"); +} +g_anim_names; + diff --git a/feely_pona_entity_create.cpp b/feely_pona_entity_create.cpp index b0cb97a..737db2d 100644 --- a/feely_pona_entity_create.cpp +++ b/feely_pona_entity_create.cpp @@ -14,6 +14,13 @@ static void FP_Entity_AddDebugEditorFlags(FP_Game *game, FP_GameEntityHandle han } } +static Dqn_f32 FP_Entity_CalcSpriteScaleForDesiredHeight(FP_Game *game, FP_Meters height, Dqn_Rect sprite_rect) +{ + Dqn_f32 sprite_in_meters = FP_Game_PixelsToMetersNx1(game, sprite_rect.size.y); + Dqn_f32 result = height.meters / sprite_in_meters; + return result; +} + static FP_GameEntityHandle FP_Entity_CreateWaypointF(FP_Game *game, Dqn_V2 pos, DQN_FMT_STRING_ANNOTATE char const *fmt, ...) { va_list args; @@ -48,7 +55,8 @@ static FP_GameEntityHandle FP_Entity_CreateClinger(FP_Game *game, Dqn_V2 pos, DQ entity->is_dying = false; entity->local_pos = pos; entity->sprite_height.meters = 1.6f; - entity->local_hit_box_size = FP_Game_MetersToPixelsNx2(game, 0.4f, 1.7f); + + entity->local_hit_box_size = FP_Game_MetersToPixelsNx2(game, 0.4f, entity->sprite_height.meters * .5f); FP_Entity_AddDebugEditorFlags(game, entity->handle); entity->flags |= FP_GameEntityFlag_NonTraversable; entity->flags |= FP_GameEntityFlag_Attackable; @@ -68,7 +76,7 @@ static FP_GameEntityHandle FP_Entity_CreateSmoochie(FP_Game *game, Dqn_V2 pos, D entity->is_dying = false; entity->local_pos = pos; entity->sprite_height.meters = 1.6f; - entity->local_hit_box_size = FP_Game_MetersToPixelsNx2(game, 0.4f, 1.6f); + entity->local_hit_box_size = FP_Game_MetersToPixelsNx2(game, 0.4f, entity->sprite_height.meters * .6f); FP_Entity_AddDebugEditorFlags(game, entity->handle); entity->flags |= FP_GameEntityFlag_NonTraversable; entity->flags |= FP_GameEntityFlag_Attackable; @@ -131,7 +139,7 @@ static FP_GameEntityHandle FP_Entity_CreateTerry(FP_Game *game, Dqn_V2 pos, DQN_ entity->type = FP_EntityType_Terry; entity->local_pos = pos; entity->sprite_height.meters = 1.8f; - entity->local_hit_box_size = FP_Game_MetersToPixelsNx2(game, 0.5f, entity->sprite_height.meters); + entity->local_hit_box_size = FP_Game_MetersToPixelsNx2(game, 0.5f, entity->sprite_height.meters * .6f); FP_Entity_AddDebugEditorFlags(game, result); entity->flags |= FP_GameEntityFlag_NonTraversable; entity->flags |= FP_GameEntityFlag_Attackable; @@ -166,8 +174,14 @@ static FP_GameEntityHandle FP_Entity_CreateClubTerry(FP_Game *game, Dqn_V2 pos, entity->type = FP_EntityType_ClubTerry; entity->local_pos = pos; - entity->local_hit_box_size = Dqn_V2_InitNx2(50, 50); - entity->sprite_height.meters = 3.66f; + entity->sprite_height.meters = 4.f; + + TELY_AssetSpriteAnimation *sprite_anim = TELY_Asset_GetSpriteAnimation(&game->club_terry_sprite_sheet, g_anim_names.club_terry_alive); + Dqn_Rect sprite_rect = game->club_terry_sprite_sheet.rects.data[sprite_anim->index]; + Dqn_f32 size_scale = FP_Entity_CalcSpriteScaleForDesiredHeight(game, entity->sprite_height, sprite_rect); + Dqn_V2 sprite_rect_scaled = sprite_rect.size * size_scale; + + entity->local_hit_box_size = Dqn_V2_InitNx2(sprite_rect_scaled.w, sprite_rect_scaled.h - (sprite_rect_scaled.h * .4f)); FP_Entity_AddDebugEditorFlags(game, result); entity->flags |= FP_GameEntityFlag_NonTraversable; diff --git a/feely_pona_game.cpp b/feely_pona_game.cpp index 6b2bcc1..e1e1463 100644 --- a/feely_pona_game.cpp +++ b/feely_pona_game.cpp @@ -651,19 +651,19 @@ static Dqn_V2 FP_Game_CalcWaypointWorldPos(FP_Game *game, FP_GameWaypoint const Dqn_Rect entity_rect = FP_Game_CalcEntityWorldHitBox(game, waypoint_entity->handle); switch (waypoint->type_direction) { case FP_GameDirection_Up: { - result = Dqn_V2_InitNx2(entity_rect.pos.x + entity_rect.size.w * .5f, entity_rect.pos.y - entity_rect.size.h * .5f); + 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: { - 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 * .5f)); + 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); } break; case FP_GameDirection_Left: { - result = Dqn_V2_InitNx2(entity_rect.pos.x - entity_rect.size.w * .5f, entity_rect.pos.y + entity_rect.size.h * .5f); + result = Dqn_V2_InitNx2(entity_rect.pos.x - entity_rect.size.w * .1f, entity_rect.pos.y + entity_rect.size.h * .5f); } break; case FP_GameDirection_Right: { - result = Dqn_V2_InitNx2(entity_rect.pos.x + entity_rect.size.w + entity_rect.size.w * .5f, entity_rect.pos.y + entity_rect.size.h * .5f); + 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); } break; case FP_GameDirection_Count: DQN_INVALID_CODE_PATH; break; @@ -674,4 +674,31 @@ static Dqn_V2 FP_Game_CalcWaypointWorldPos(FP_Game *game, FP_GameWaypoint const return result; } +FP_GameFindClosestEntityResult FP_Game_FindClosestEntityWithType(FP_Game *game, FP_GameEntityHandle src_entity_handle, FP_EntityType type) +{ + FP_GameFindClosestEntityResult result = {}; + FP_GameEntity *src_entity = FP_Game_GetEntity(game, src_entity_handle); + if (FP_Game_IsNilEntity(src_entity)) + return result; + + Dqn_V2 src_pos = FP_Game_CalcEntityWorldPos(game, src_entity_handle); + result.dist_squared = DQN_F32_MAX; + result.pos = Dqn_V2_InitNx1(DQN_F32_MAX); + + for (FP_GameEntityIterator it = {}; FP_Game_DFSPostOrderWalkEntityTree(game, &it, game->root_entity); ) { + FP_GameEntity *it_entity = it.entity; + if (it_entity->type != type) + continue; + + Dqn_V2 pos = FP_Game_CalcEntityWorldPos(game, it_entity->handle); + Dqn_f32 dist = Dqn_V2_LengthSq_V2x2(pos, src_pos); + if (dist < result.dist_squared) { + result.pos = pos; + result.dist_squared = dist; + result.entity = it_entity->handle; + } + } + + return result; +} diff --git a/feely_pona_game.h b/feely_pona_game.h index c1a84da..a70c7a2 100644 --- a/feely_pona_game.h +++ b/feely_pona_game.h @@ -16,6 +16,7 @@ enum FP_GameEntityFlag FP_GameEntityFlag_MobSpawnerWaypoint = 1 << 8, FP_GameEntityFlag_AggrosWhenNearTerry = 1 << 9, FP_GameEntityFlag_Attackable = 1 << 10, + FP_GameEntityFlag_RespondsToClubTerry = 1 << 11, }; enum FP_GameShapeType @@ -151,8 +152,8 @@ struct FP_GameEntity Dqn_V2 attack_box_size; Dqn_V2 attack_box_offset; - bool attack_processed; - bool is_dying; + bool attack_processed; + bool is_dying; Dqn_FArray spawner_waypoints; FP_SentinelList spawn_list; @@ -235,3 +236,11 @@ struct FP_GameAStarNode Dqn_V2I came_from; bool non_traversable; }; + +struct FP_GameFindClosestEntityResult +{ + FP_GameEntityHandle entity; + Dqn_f32 dist_squared; + Dqn_V2 pos; +}; +