fp: Add projectile attack

This commit is contained in:
doyle 2023-10-05 21:10:50 +11:00
parent aed29a9f50
commit 5f8344ddda
7 changed files with 103 additions and 23 deletions

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

Binary file not shown.

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

Binary file not shown.

View File

@ -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
}
}
}

View File

@ -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,
};

View File

@ -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;
}

View File

@ -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);

View File

@ -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<FP_GameShape, 4> shapes;
Dqn_FArray<FP_GameEntityHandle, 4> projectiles;
uint64_t ttl_end_timestamp;
};
struct FP_GameEntityIterator