diff --git a/Data/Textures/atlas.txt b/Data/Textures/atlas.txt index c5e6316..e92c32d 100644 --- a/Data/Textures/atlas.txt +++ b/Data/Textures/atlas.txt @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1c3a307d8e21e2905c612e367a31533dce2335a4dcdbcfa2714a696cf61f35d4 +oid sha256:7bdca69d3d7e98d739cb4922e95923218c65b3629f6aaf024efffb47fa75cd09 size 4579 diff --git a/Data/Textures/sprite_spec.txt b/Data/Textures/sprite_spec.txt index c7c2d80..d490acf 100644 --- a/Data/Textures/sprite_spec.txt +++ b/Data/Textures/sprite_spec.txt @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:df800282c974e6c1792991ff33704c9a3f6574dc3829048f4f2166ef7f4e0cb8 +oid sha256:868bda4e10fd1a8f2d32f8b9cb5eea3ee25d1809b9820dd6d2caceae49d00a50 size 1333 diff --git a/feely_pona.cpp b/feely_pona.cpp index 6ec7d05..a3c8991 100644 --- a/feely_pona.cpp +++ b/feely_pona.cpp @@ -207,6 +207,7 @@ static void FP_Game_MoveEntity(FP_Game *game, FP_GameEntityHandle entity_handle, case FP_EntityType_AirportTerry: break; case FP_EntityType_ChurchTerry: break; case FP_EntityType_KennelTerry: break; + case FP_EntityType_PhoneMessageProjectile: break; } if (!entity_collides_with_collider) @@ -214,6 +215,9 @@ static void FP_Game_MoveEntity(FP_Game *game, FP_GameEntityHandle entity_handle, // NOTE: Sweep collider with half the radius of the source entity Dqn_Rect collider_world_hit_box = FP_Game_CalcEntityWorldHitBox(game, collider->handle); + if (Dqn_V2_Area(collider_world_hit_box.size) <= 0) + continue; + Dqn_Rect swept_collider_world_hit_box = collider_world_hit_box; swept_collider_world_hit_box.pos -= (entity_world_hit_box.size * .5f); swept_collider_world_hit_box.size += entity_world_hit_box.size; @@ -524,6 +528,9 @@ void FP_EntityActionStateMachine(FP_Game *game, TELY_Audio *audio, TELY_Platform if (TELY_Platform_InputScanCodeIsPressed(input, TELY_PlatformInputScanCode_J) || TELY_Platform_InputGamepadButtonCodeIsPressed(input, 0, TELY_PlatformInputGamepadButtonCode_X)) { FP_Game_EntityTransitionState(game, entity, FP_EntityTerryState_Attack); + } else if (TELY_Platform_InputScanCodeIsPressed(input, TELY_PlatformInputScanCode_K) || + TELY_Platform_InputGamepadButtonCodeIsPressed(input, 0, TELY_PlatformInputGamepadButtonCode_Y)) { + FP_Game_EntityTransitionState(game, entity, FP_EntityTerryState_RangeAttack); } else if (acceleration_meters_per_s->x || acceleration_meters_per_s->y) { FP_Game_EntityTransitionState(game, entity, FP_EntityTerryState_Run); } @@ -541,7 +548,7 @@ void FP_EntityActionStateMachine(FP_Game *game, TELY_Audio *audio, TELY_Platform } } break; - case FP_EntityTerryState_AttackPhone: { + case FP_EntityTerryState_RangeAttack: { if (entering_new_state) { uint64_t duration_ms = render_data.sprite.anim->count * render_data.sprite.anim->ms_per_frame; FP_Game_EntityActionReset(game, entity->handle, duration_ms, render_data.sprite); @@ -562,6 +569,9 @@ void FP_EntityActionStateMachine(FP_Game *game, TELY_Audio *audio, TELY_Platform if (TELY_Platform_InputScanCodeIsPressed(input, TELY_PlatformInputScanCode_J) || TELY_Platform_InputGamepadButtonCodeIsPressed(input, 0, TELY_PlatformInputGamepadButtonCode_X)) { FP_Game_EntityTransitionState(game, entity, FP_EntityTerryState_Attack); + } else if (TELY_Platform_InputScanCodeIsPressed(input, TELY_PlatformInputScanCode_K) || + TELY_Platform_InputGamepadButtonCodeIsPressed(input, 0, TELY_PlatformInputGamepadButtonCode_Y)) { + FP_Game_EntityTransitionState(game, entity, FP_EntityTerryState_RangeAttack); } else if (TELY_Platform_InputScanCodeIsPressed(input, TELY_PlatformInputScanCode_LeftControl) || TELY_Platform_InputGamepadButtonCodeIsPressed(input, 0, TELY_PlatformInputGamepadButtonCode_A)) { FP_Game_EntityTransitionState(game, entity, FP_EntityTerryState_Dash); @@ -585,7 +595,7 @@ void FP_EntityActionStateMachine(FP_Game *game, TELY_Audio *audio, TELY_Platform } break; } - if (*state == FP_EntityTerryState_Attack || *state == FP_EntityTerryState_AttackPhone) { + if (*state == FP_EntityTerryState_Attack || *state == FP_EntityTerryState_RangeAttack) { DQN_ASSERT(action->sprite.anim); uint64_t duration_ms = action->sprite.anim->count * action->sprite.anim->ms_per_frame; @@ -594,33 +604,46 @@ void FP_EntityActionStateMachine(FP_Game *game, TELY_Audio *audio, TELY_Platform // NOTE: Adding an attack_processed bool to make sure things only fire once if (!entity->attack_processed && game->clock_ms >= midpoint_clock_ms) { - entity->attack_box_size = entity->local_hit_box_size; - TELY_Audio_Play(audio, game->audio[FP_GameAudio_TerryHit], 1.f); // NOTE: Position the attack box + Dqn_V2 dir_vector = {}; switch (entity->direction) { case FP_GameDirection_Left: { + dir_vector.x = -1.f; entity->attack_box_offset = Dqn_V2_InitNx2(entity->local_hit_box_offset.x - entity->attack_box_size.w, entity->local_hit_box_offset.y); } break; case FP_GameDirection_Right: { + dir_vector.x = +1.f; entity->attack_box_offset = Dqn_V2_InitNx2(entity->local_hit_box_offset.x + entity->attack_box_size.w, entity->local_hit_box_offset.y); } break; case FP_GameDirection_Up: { + dir_vector.y = -1.f; entity->attack_box_offset = Dqn_V2_InitNx2(entity->local_hit_box_offset.x, entity->local_hit_box_offset.y - entity->attack_box_size.h); } break; case FP_GameDirection_Down: { + dir_vector.y = +1.f; entity->attack_box_offset = Dqn_V2_InitNx2(entity->local_hit_box_offset.x, entity->local_hit_box_offset.y + entity->attack_box_size.h); } break; case FP_GameDirection_Count: break; } + + if (*state == FP_EntityTerryState_RangeAttack) { + Dqn_Rect entity_hit_box = FP_Game_CalcEntityWorldHitBox(game, entity->handle); + Dqn_V2 projectile_pos = entity_hit_box.pos + entity->attack_box_offset; + Dqn_V2 projectile_acceleration = FP_Game_MetersToPixelsV2(game, dir_vector * 0.25f); + FP_Entity_CreatePhoneMessageProjectile(game, projectile_pos, projectile_acceleration, "Phone Message Projectile"); + } else { + entity->attack_box_size = entity->local_hit_box_size; + TELY_Audio_Play(audio, game->audio[FP_GameAudio_TerryHit], 1.f); + } entity->attack_processed = true; } else { entity->attack_box_size = {}; @@ -723,6 +746,7 @@ void FP_EntityActionStateMachine(FP_Game *game, TELY_Audio *audio, TELY_Platform if (entering_new_state) { uint64_t duration_ms = render_data.sprite.anim->count * render_data.sprite.anim->ms_per_frame; FP_Game_EntityActionReset(game, entity->handle, duration_ms, render_data.sprite); + entity->local_hit_box_size = {}; } if (action_has_finished) @@ -827,6 +851,7 @@ void FP_EntityActionStateMachine(FP_Game *game, TELY_Audio *audio, TELY_Platform TELY_AssetAnimatedSprite sprite = TELY_Asset_MakeAnimatedSprite(sheet, g_anim_names.clinger_death, TELY_AssetFlip_No); uint64_t duration_ms = sprite.anim->count * sprite.anim->ms_per_frame; FP_Game_EntityActionReset(game, entity->handle, duration_ms, sprite); + entity->local_hit_box_size = {}; } if (action_has_finished) @@ -1082,6 +1107,7 @@ void FP_EntityActionStateMachine(FP_Game *game, TELY_Audio *audio, TELY_Platform if (entering_new_state) { uint64_t duration_ms = render_data.sprite.anim->count * render_data.sprite.anim->ms_per_frame; FP_Game_EntityActionReset(game, entity->handle, duration_ms, render_data.sprite); + entity->local_hit_box_size = {}; } if (action_has_finished) @@ -1241,7 +1267,7 @@ void FP_Update(TELY_Platform *platform, FP_Game *game, TELY_PlatformInput *input continue; // NOTE: Move entity by keyboard and gamepad =============================================== - Dqn_V2 acceleration_meters_per_s = {}; + Dqn_V2 acceleration_meters_per_s = entity->constant_acceleration_per_s; if (game->clicked_entity == entity->handle) { if (entity->flags & (FP_GameEntityFlag_MoveByKeyboard | FP_GameEntityFlag_MoveByGamepad)) { bool move_entity = true; @@ -1261,15 +1287,20 @@ 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_Nil: break; - case FP_EntityType_ClubTerry: break; - case FP_EntityType_Count: break; - case FP_EntityType_Map: break; - case FP_EntityType_MerchantTerry: break; - case FP_EntityType_MerchantGraveyard: break; - case FP_EntityType_MerchantPhoneCompany: break; - case FP_EntityType_Heart: break; - case FP_EntityType_MerchantGym: break; + case FP_EntityType_Nil: break; + case FP_EntityType_ClubTerry: break; + case FP_EntityType_Count: break; + case FP_EntityType_Map: break; + case FP_EntityType_MerchantTerry: break; + case FP_EntityType_MerchantGraveyard: break; + case FP_EntityType_MerchantPhoneCompany: break; + case FP_EntityType_Heart: break; + case FP_EntityType_MerchantGym: break; + case FP_EntityType_AirportTerry: break; + case FP_EntityType_Catfish: break; + case FP_EntityType_ChurchTerry: break; + case FP_EntityType_KennelTerry: break; + case FP_EntityType_PhoneMessageProjectile: break; } if (move_entity) { @@ -1536,7 +1567,7 @@ void FP_Update(TELY_Platform *platform, FP_Game *game, TELY_PlatformInput *input if (game->active_entity == entity->handle && entity->flags & FP_GameEntityFlag_MoveByMouse) { entity->velocity = {}; acceleration_meters_per_s = {}; - entity->local_pos += input->mouse_p_delta; + entity->local_pos += input->mouse_p_delta; } if (game->clicked_entity == entity->handle) { @@ -1596,6 +1627,13 @@ void FP_Update(TELY_Platform *platform, FP_Game *game, TELY_PlatformInput *input for (FP_GameEntityIterator it = {}; FP_Game_DFSPostOrderWalkEntityTree(game, &it, game->root_entity); ) { FP_GameEntity *entity = it.entity; + if (entity->flags & FP_GameEntityFlag_TTL) { + if (game->clock_ms >= entity->ttl_end_timestamp) { + FP_Game_DeleteEntity(game, entity->handle); + continue; + } + } + // NOTE: Derive dynmamic bounding boxes ==================================================== if (entity->flags & FP_GameEntityFlag_DeriveHitBoxFromChildrenBoundingBox) { Dqn_Rect children_bbox = {}; @@ -1727,7 +1765,8 @@ void FP_Update(TELY_Platform *platform, FP_Game *game, TELY_PlatformInput *input case FP_EntityType_Heart: break; case FP_EntityType_AirportTerry: case FP_EntityType_ChurchTerry: - case FP_EntityType_KennelTerry: break; + case FP_EntityType_KennelTerry: break; + case FP_EntityType_PhoneMessageProjectile: break; } if (!permit_attack) @@ -1743,6 +1782,7 @@ void FP_Update(TELY_Platform *platform, FP_Game *game, TELY_PlatformInput *input defender->is_dying = true; // NOTE: Kickback ====================================================================== + #if 0 Dqn_V2 defender_world_pos = Dqn_Rect_Center(defender_box); Dqn_V2 attack_dir_vector = {}; if (attacker_world_pos.x < defender_world_pos.x) @@ -1752,6 +1792,7 @@ void FP_Update(TELY_Platform *platform, FP_Game *game, TELY_PlatformInput *input Dqn_V2 attack_acceleration_meters_per_s = attack_dir_vector * 60.f; FP_Game_MoveEntity(game, defender->handle, attack_acceleration_meters_per_s); + #endif } } } diff --git a/feely_pona_entity.h b/feely_pona_entity.h index 3f5ef5f..77043b8 100644 --- a/feely_pona_entity.h +++ b/feely_pona_entity.h @@ -20,6 +20,7 @@ enum FP_EntityType FP_EntityType_MerchantTerry, FP_EntityType_Smoochie, FP_EntityType_Terry, + FP_EntityType_PhoneMessageProjectile, FP_EntityType_Count, }; @@ -28,7 +29,7 @@ enum FP_EntityTerryState FP_EntityTerryState_Nil, FP_EntityTerryState_Idle, FP_EntityTerryState_Attack, - FP_EntityTerryState_AttackPhone, + FP_EntityTerryState_RangeAttack, FP_EntityTerryState_Run, FP_EntityTerryState_Dash, }; diff --git a/feely_pona_entity_create.cpp b/feely_pona_entity_create.cpp index c0e463e..e6f9697 100644 --- a/feely_pona_entity_create.cpp +++ b/feely_pona_entity_create.cpp @@ -39,12 +39,12 @@ FP_EntityRenderData FP_Entity_GetRenderData(FP_Game *game, FP_EntityType type, u } } break; - case FP_EntityTerryState_AttackPhone: { + case FP_EntityTerryState_RangeAttack: { switch (direction) { case FP_GameDirection_Up: result.anim_name = g_anim_names.terry_attack_phone_up; break; case FP_GameDirection_Down: result.anim_name = g_anim_names.terry_attack_phone_down; break; - case FP_GameDirection_Left: result.anim_name = g_anim_names.terry_attack_phone_side; break; - case FP_GameDirection_Right: result.anim_name = g_anim_names.terry_attack_phone_side; result.flip = TELY_AssetFlip_X; break; + case FP_GameDirection_Left: result.anim_name = g_anim_names.terry_attack_phone_side; result.flip = TELY_AssetFlip_X; break; + case FP_GameDirection_Right: result.anim_name = g_anim_names.terry_attack_phone_side; break; case FP_GameDirection_Count: DQN_INVALID_CODE_PATH; break; } } break; @@ -227,6 +227,11 @@ FP_EntityRenderData FP_Entity_GetRenderData(FP_Game *game, FP_EntityType type, u } } break; + case FP_EntityType_PhoneMessageProjectile: { + result.height.meters = 1.f; + result.anim_name = g_anim_names.terry_attack_phone_message; + } break; + case FP_EntityType_Count: DQN_INVALID_CODE_PATH; break; } @@ -660,3 +665,32 @@ static FP_GameEntityHandle FP_Entity_CreateAirportTerry(FP_Game *game, Dqn_V2 po entity->local_hit_box_size = Dqn_V2_InitNx2(sprite_rect_scaled.w, sprite_rect_scaled.h - (sprite_rect_scaled.h * .4f)); return result; } + +static FP_GameEntityHandle FP_Entity_CreatePhoneMessageProjectile(FP_Game *game, Dqn_V2 pos, Dqn_V2 velocity, 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_PhoneMessageProjectile; + FP_EntityRenderData render_data = FP_Entity_GetRenderData(game, entity->type, 0, FP_GameDirection_Down); + + entity->constant_acceleration_per_s = velocity; + entity->local_pos = pos; + entity->sprite_height = render_data.height; + FP_Entity_AddDebugEditorFlags(game, result); + entity->flags |= FP_GameEntityFlag_TTL; + + entity->local_hit_box_offset = Dqn_V2_InitNx2(0, render_data.render_size.h * .1f); + entity->local_hit_box_size = Dqn_V2_InitNx2(render_data.render_size.w, render_data.render_size.h - (render_data.render_size.h * .4f)); + entity->attack_box_offset = entity->local_hit_box_offset; + entity->attack_box_size = entity->local_hit_box_size; + entity->ttl_end_timestamp = game->clock_ms + 1000; + + uint64_t duration_ms = FP_GAME_ENTITY_ACTION_INFINITE_TIMER; + FP_Game_EntityActionReset(game, result, duration_ms, render_data.sprite); + + return result; +} diff --git a/feely_pona_game.cpp b/feely_pona_game.cpp index 42e22fc..8a3f15d 100644 --- a/feely_pona_game.cpp +++ b/feely_pona_game.cpp @@ -388,7 +388,7 @@ static Dqn_Rect FP_Game_CalcEntityWorldHitBox(FP_Game const *game, FP_GameEntity static Dqn_Rect FP_Game_CalcEntityAttackWorldHitBox(FP_Game const *game, FP_GameEntityHandle handle) { - FP_GameEntity *entity = FP_Game_GetEntity(DQN_CAST(FP_Game *) game, handle); + FP_GameEntity *entity = FP_Game_GetEntity(DQN_CAST(FP_Game *) game, handle); Dqn_V2 world_pos = FP_Game_CalcEntityWorldPos(game, handle); Dqn_V2 half_hit_box_size = entity->attack_box_size * .5f; Dqn_Rect result = Dqn_Rect_InitV2x2(world_pos + entity->attack_box_offset - half_hit_box_size, entity->attack_box_size); diff --git a/feely_pona_game.h b/feely_pona_game.h index 50b1402..a693dce 100644 --- a/feely_pona_game.h +++ b/feely_pona_game.h @@ -22,6 +22,7 @@ enum FP_GameEntityFlag FP_GameEntityFlag_PointOfInterestHeart = 1 << 11, FP_GameEntityFlag_CameraTracking = 1 << 14, FP_GameEntityFlag_BuildZone = 1 << 15, + FP_GameEntityFlag_TTL = 1 << 16, }; enum FP_GameShapeType @@ -150,6 +151,7 @@ struct FP_GameEntity FP_Meters sprite_height; FP_GameEntityAction action; FP_Meters base_acceleration_per_s; + Dqn_V2 constant_acceleration_per_s; Dqn_V2 velocity; // Extra animations that are to be executed, but, don't affect the state @@ -189,6 +191,8 @@ struct FP_GameEntity Dqn_V2 local_pos; Dqn_f64 alive_time_s; Dqn_FArray shapes; + Dqn_FArray projectiles; + uint64_t ttl_end_timestamp; }; struct FP_GameEntityIterator