fp: Load heart & remove aggro when terry distances himself

This commit is contained in:
doyle 2023-10-01 16:11:08 +11:00
parent f258886d68
commit 0234d34673
18 changed files with 154 additions and 15 deletions

BIN
Data/Textures/heart_resized_25%.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
Data/Textures/heart_resized_25%.txt (Stored with Git LFS) Normal file

Binary file not shown.

BIN
Data/Textures/heart_resized_25%/heart_1.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
Data/Textures/heart_resized_25%/heart_2.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
Data/Textures/heart_resized_25%/heart_3.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
Data/Textures/heart_resized_25%/heart_bleed_1.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
Data/Textures/heart_resized_25%/heart_bleed_2.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
Data/Textures/heart_resized_25%/heart_bleed_3.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
Data/Textures/heart_resized_25%/heart_bleed_4.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
Data/Textures/heart_resized_25%/heart_bleed_5.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
Data/Textures/heart_resized_25%/heart_bleed_6.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
Data/Textures/sprite_spec.txt (Stored with Git LFS)

Binary file not shown.

View File

@ -16,3 +16,4 @@ if not exist "%sprite_packer%" (
%sprite_packer% 4096x2048 %script_dir%\Data\Textures\sprite_spec.txt %script_dir%\Data\Textures\clinger_resized_25%% || exit /b 1
%sprite_packer% 4096x2048 %script_dir%\Data\Textures\sprite_spec.txt %script_dir%\Data\Textures\club_terry_resized_25%% || exit /b 1
%sprite_packer% 4096x2048 %script_dir%\Data\Textures\sprite_spec.txt %script_dir%\Data\Textures\map_resized_25%% || exit /b 1
%sprite_packer% 8192x8192 %script_dir%\Data\Textures\sprite_spec.txt %script_dir%\Data\Textures\heart_resized_25%% || exit /b 1

View File

@ -277,6 +277,7 @@ void TELY_DLL_Init(void *user_data)
game->clinger_sprite_sheet = FP_LoadSpriteSheetFromSpec(platform, assets, &platform->arena, DQN_STRING8("clinger_resized_25%"));
game->club_terry_sprite_sheet = FP_LoadSpriteSheetFromSpec(platform, assets, &platform->arena, DQN_STRING8("club_terry_resized_25%"));
game->map_sprite_sheet = FP_LoadSpriteSheetFromSpec(platform, assets, &platform->arena, DQN_STRING8("map_resized_25%"));
game->heart_sprite_sheet = FP_LoadSpriteSheetFromSpec(platform, assets, &platform->arena, DQN_STRING8("heart_resized_25%"));
}
game->entities = Dqn_VArray_Init<FP_GameEntity>(&platform->arena, 1024 * 8);
@ -388,6 +389,7 @@ void TELY_DLL_Init(void *user_data)
FP_Game_PopParentEntity(game);
}
FP_Entity_CreateHeart(game, base_mid_p, "Heart");
uint16_t font_size = 18;
game->camera.scale = Dqn_V2_InitNx1(1);
@ -943,9 +945,27 @@ void FP_EntityActionStateMachine(FP_Game *game, TELY_Audio *audio, TELY_Platform
} break;
case FP_EntityType_Heart: {
FP_EntityHeartState *state = DQN_CAST(FP_EntityHeartState *) & action->state;
TELY_AssetSpriteSheet *sheet = &game->heart_sprite_sheet;
switch (*state) {
case FP_EntityHeartState_Nil: {
action->next_state = FP_EntityHeartState_Idle;
} break;
case FP_EntityHeartState_Idle: {
if (entering_new_state) {
TELY_AssetAnimatedSprite sprite = TELY_Asset_MakeAnimatedSprite(sheet, g_anim_names.heart, TELY_AssetFlip_No);
uint64_t duration_ms = FP_GAME_ENTITY_ACTION_INFINITE_TIMER;
FP_Game_EntityActionReset(game, entity->handle, duration_ms, sprite);
}
} break;
}
} break;
case FP_EntityType_Nil: break;
case FP_EntityType_Count: DQN_INVALID_CODE_PATH; break;
}
}
@ -1058,9 +1078,11 @@ 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) {
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_GameFindClosestEntityResult closest_result = FP_Game_FindClosestEntityWithType(game, entity->handle, FP_EntityType_Terry);
Dqn_f32 aggro_dist_threshold = FP_Game_MetersToPixelsNx1(game, 4.f);
if (closest_result.dist_squared < DQN_SQUARED(aggro_dist_threshold)) {
bool has_waypoint_to_terry = false;
for (FP_SentinelListLink<FP_GameWaypoint> *link = nullptr;
!has_waypoint_to_terry && FP_SentinelList_Iterate<FP_GameWaypoint>(&entity->waypoints, &link); ) {
@ -1091,6 +1113,15 @@ void FP_Update(TELY_Platform *platform, FP_Game *game, TELY_PlatformInput *input
terry->aggro_slot[aggro_direction] = entity->handle;
}
}
} else {
if (closest_result.dist_squared > DQN_SQUARED(aggro_dist_threshold * 2.f)) {
for (FP_SentinelListLink<FP_GameWaypoint> *link = nullptr; FP_SentinelList_Iterate<FP_GameWaypoint>(&entity->waypoints, &link); ) {
FP_GameEntity *maybe_terry = FP_Game_GetEntity(game, link->data.entity);
if (maybe_terry->type == FP_EntityType_Terry) {
link = FP_SentinelList_Erase(&entity->waypoints, link, game->chunk_pool);
}
}
}
}
}
@ -1127,6 +1158,39 @@ 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, 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->chunk_pool);
FP_GameWaypoint *waypoint = &link->data;
waypoint->entity = closest_heart.entity;
waypoint->type = FP_GameWaypointType_Side;
if (entity_pos.x <= club_top_left.x) {
waypoint->type_direction = FP_GameDirection_Left;
} else if (entity_pos.x >= club_top_right.x) {
waypoint->type_direction = FP_GameDirection_Right;
} else if (entity_pos.y <= club_top_left.y) {
waypoint->type_direction = FP_GameDirection_Up;
} else {
waypoint->type_direction = FP_GameDirection_Down;
}
}
}
}
while (entity->waypoints.size) {
FP_SentinelListLink<FP_GameWaypoint> *waypoint_link = entity->waypoints.sentinel->next;
FP_GameWaypoint const *waypoint = &waypoint_link->data;
@ -1163,10 +1227,14 @@ void FP_Update(TELY_Platform *platform, FP_Game *game, TELY_PlatformInput *input
// NOTE: We have arrived at the waypoint
bool aggro_on_terry = (entity->flags & FP_GameEntityFlag_AggrosWhenNearTerry) && waypoint_entity->type == FP_EntityType_Terry;
bool club_terry_response = (entity->flags & FP_GameEntityFlag_RespondsToClubTerry) &&
((entity->flags & FP_GameEntityFlag_ExperiencedClubTerry) == 0) &&
waypoint_entity->type == FP_EntityType_ClubTerry;
if (((waypoint->flags & FP_GameWaypointFlag_NonInterruptible) == 0) && (aggro_on_terry || club_terry_response)) {
bool heart_response = (entity->flags & FP_GameEntityFlag_PointOfInterestHeart) && waypoint_entity->type == FP_EntityType_Heart;
if (((waypoint->flags & FP_GameWaypointFlag_NonInterruptible) == 0) && (aggro_on_terry || club_terry_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 (club_terry_response) {
@ -1230,6 +1298,7 @@ void FP_Update(TELY_Platform *platform, FP_Game *game, TELY_PlatformInput *input
case FP_EntityType_Count: DQN_INVALID_CODE_PATH; break;
case FP_EntityType_ClubTerry: break;
case FP_EntityType_Map: break;
case FP_EntityType_Heart: break;
}
}

View File

@ -42,17 +42,20 @@ struct FP_GlobalAnimations
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 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");
Dqn_String8 club_terry_alive = DQN_STRING8("club_terry_alive");
Dqn_String8 club_terry_dark = DQN_STRING8("club_terry_dark");
Dqn_String8 map = DQN_STRING8("map");
Dqn_String8 heart = DQN_STRING8("heart");
Dqn_String8 heart_bleed = DQN_STRING8("heart_bleed");
Dqn_String8 map = DQN_STRING8("map");
}
g_anim_names;

View File

@ -12,6 +12,7 @@ enum FP_EntityType
FP_EntityType_Merchant,
FP_EntityType_ClubTerry,
FP_EntityType_Clinger,
FP_EntityType_Heart,
FP_EntityType_Count,
};
@ -62,3 +63,9 @@ enum FP_EntityMapState
FP_EntityMapState_Nil,
FP_EntityMapState_Idle,
};
enum FP_EntityHeartState
{
FP_EntityHeartState_Nil,
FP_EntityHeartState_Idle,
};

View File

@ -191,3 +191,27 @@ static FP_GameEntityHandle FP_Entity_CreateClubTerry(FP_Game *game, Dqn_V2 pos,
return result;
}
static FP_GameEntityHandle FP_Entity_CreateHeart(FP_Game *game, Dqn_V2 pos, DQN_FMT_STRING_ANNOTATE char const *fmt, ...)
{
va_list args;
va_start(args, fmt);
FP_GameEntity *entity = FP_Game_MakeEntityPointerFV(game, fmt, args);
FP_GameEntityHandle result = entity->handle;
va_end(args);
entity->type = FP_EntityType_Heart;
entity->local_pos = pos;
entity->sprite_height.meters = 4.f;
TELY_AssetSpriteAnimation *sprite_anim = TELY_Asset_GetSpriteAnimation(&game->heart_sprite_sheet, g_anim_names.heart);
Dqn_Rect sprite_rect = game->heart_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.w * .1f), sprite_rect_scaled.h);
FP_Entity_AddDebugEditorFlags(game, result);
entity->flags |= FP_GameEntityFlag_NonTraversable;
return result;
}

View File

@ -19,6 +19,7 @@ enum FP_GameEntityFlag
FP_GameEntityFlag_RespondsToClubTerry = 1 << 11,
FP_GameEntityFlag_PartyingAtClubTerry = 1 << 12,
FP_GameEntityFlag_ExperiencedClubTerry = 1 << 13,
FP_GameEntityFlag_PointOfInterestHeart = 1 << 11,
FP_GameEntityFlag_CameraTracking = 1 << 14,
};
@ -230,6 +231,7 @@ struct FP_Game
TELY_AssetSpriteSheet clinger_sprite_sheet;
TELY_AssetSpriteSheet club_terry_sprite_sheet;
TELY_AssetSpriteSheet map_sprite_sheet;
TELY_AssetSpriteSheet heart_sprite_sheet;
FP_GameEntity *root_entity;
FP_GameEntity *entity_free_list;