fp: Wire up attack chaining
This commit is contained in:
parent
3e31d0ef69
commit
12e6e5d6f1
2
External/tely
vendored
2
External/tely
vendored
@ -1 +1 @@
|
|||||||
Subproject commit 0b5b08bb635d1aad6832a69d30e2c45f7a59af8b
|
Subproject commit 964aa47a6ed2e2bb1606dd576a57867d09ca8ba6
|
179
feely_pona.cpp
179
feely_pona.cpp
@ -60,26 +60,26 @@ void TELY_DLL_Init(void *user_data)
|
|||||||
sheet->sprite_size = Dqn_V2I_InitNx2(50, 37);
|
sheet->sprite_size = Dqn_V2I_InitNx2(50, 37);
|
||||||
|
|
||||||
TELY_AssetSpriteAnimation hero_anims[] = {
|
TELY_AssetSpriteAnimation hero_anims[] = {
|
||||||
{DQN_STRING8("Everything"), /*index*/ 0, /*count*/ sheet->sprite_count},
|
{DQN_STRING8("Everything"), /*index*/ 0, /*count*/ sheet->sprite_count, /*seconds_per_frame*/ 1 / 12.f},
|
||||||
{DQN_STRING8("Idle"), /*index*/ 0, /*count*/ 3},
|
{DQN_STRING8("Idle"), /*index*/ 0, /*count*/ 3, /*seconds_per_frame*/ 1 / 4.f},
|
||||||
{DQN_STRING8("Run"), /*index*/ 8, /*count*/ 6},
|
{DQN_STRING8("Run"), /*index*/ 8, /*count*/ 6, /*seconds_per_frame*/ 1 / 8.f},
|
||||||
{DQN_STRING8("Jump"), /*index*/ 14, /*count*/ 10},
|
{DQN_STRING8("Jump"), /*index*/ 14, /*count*/ 10, /*seconds_per_frame*/ 1 / 12.f},
|
||||||
{DQN_STRING8("Floor slide"), /*index*/ 24, /*count*/ 5},
|
{DQN_STRING8("Floor slide"), /*index*/ 24, /*count*/ 5, /*seconds_per_frame*/ 1 / 12.f},
|
||||||
{DQN_STRING8("Unknown"), /*index*/ 29, /*count*/ 9},
|
{DQN_STRING8("Unknown"), /*index*/ 29, /*count*/ 9, /*seconds_per_frame*/ 1 / 12.f},
|
||||||
{DQN_STRING8("Attack A"), /*index*/ 38, /*count*/ 11},
|
{DQN_STRING8("Attack A"), /*index*/ 42, /*count*/ 7, /*seconds_per_frame*/ 1 / 12.f},
|
||||||
{DQN_STRING8("Attack B"), /*index*/ 49, /*count*/ 4},
|
{DQN_STRING8("Attack B"), /*index*/ 49, /*count*/ 4, /*seconds_per_frame*/ 1 / 8.f},
|
||||||
{DQN_STRING8("Attack C"), /*index*/ 53, /*count*/ 6},
|
{DQN_STRING8("Attack C"), /*index*/ 53, /*count*/ 6, /*seconds_per_frame*/ 1 / 12.f},
|
||||||
{DQN_STRING8("Hurt A"), /*index*/ 59, /*count*/ 5},
|
{DQN_STRING8("Hurt A"), /*index*/ 59, /*count*/ 5, /*seconds_per_frame*/ 1 / 12.f},
|
||||||
{DQN_STRING8("Hurt B"), /*index*/ 64, /*count*/ 5},
|
{DQN_STRING8("Hurt B"), /*index*/ 64, /*count*/ 5, /*seconds_per_frame*/ 1 / 12.f},
|
||||||
{DQN_STRING8("Unsheath sword"), /*index*/ 69, /*count*/ 4},
|
{DQN_STRING8("Unsheath sword"), /*index*/ 69, /*count*/ 4, /*seconds_per_frame*/ 1 / 12.f},
|
||||||
{DQN_STRING8("Sheath sword"), /*index*/ 73, /*count*/ 4},
|
{DQN_STRING8("Sheath sword"), /*index*/ 73, /*count*/ 4, /*seconds_per_frame*/ 1 / 12.f},
|
||||||
{DQN_STRING8("Air drift"), /*index*/ 77, /*count*/ 2},
|
{DQN_STRING8("Air drift"), /*index*/ 77, /*count*/ 2, /*seconds_per_frame*/ 1 / 12.f},
|
||||||
{DQN_STRING8("Air drop"), /*index*/ 79, /*count*/ 2},
|
{DQN_STRING8("Air drop"), /*index*/ 79, /*count*/ 2, /*seconds_per_frame*/ 1 / 12.f},
|
||||||
{DQN_STRING8("Ladder climb"), /*index*/ 81, /*count*/ 4},
|
{DQN_STRING8("Ladder climb"), /*index*/ 81, /*count*/ 4, /*seconds_per_frame*/ 1 / 12.f},
|
||||||
{DQN_STRING8("Chi push"), /*index*/ 85, /*count*/ 8},
|
{DQN_STRING8("Chi push"), /*index*/ 85, /*count*/ 8, /*seconds_per_frame*/ 1 / 12.f},
|
||||||
{DQN_STRING8("Leap slice A"), /*index*/ 93, /*count*/ 7},
|
{DQN_STRING8("Leap slice A"), /*index*/ 93, /*count*/ 7, /*seconds_per_frame*/ 1 / 12.f},
|
||||||
{DQN_STRING8("Leap slice B"), /*index*/ 100, /*count*/ 3},
|
{DQN_STRING8("Leap slice B"), /*index*/ 100, /*count*/ 3, /*seconds_per_frame*/ 1 / 12.f},
|
||||||
{DQN_STRING8("Leap slice C"), /*index*/ 103, /*count*/ 6},
|
{DQN_STRING8("Leap slice C"), /*index*/ 103, /*count*/ 6, /*seconds_per_frame*/ 1 / 12.f},
|
||||||
};
|
};
|
||||||
|
|
||||||
game->hero_sprite_anims = Dqn_Slice_Alloc<TELY_AssetSpriteAnimation>(&platform->arena, DQN_ARRAY_UCOUNT(hero_anims), Dqn_ZeroMem_No);
|
game->hero_sprite_anims = Dqn_Slice_Alloc<TELY_AssetSpriteAnimation>(&platform->arena, DQN_ARRAY_UCOUNT(hero_anims), Dqn_ZeroMem_No);
|
||||||
@ -521,104 +521,88 @@ void FP_GameUpdate(TELY_Platform *platform, FP_Game *game, TELY_Renderer *render
|
|||||||
FP_GameEntityAction *action = &entity->action;
|
FP_GameEntityAction *action = &entity->action;
|
||||||
{
|
{
|
||||||
bool we_are_clicked_entity = entity->handle == game->clicked_entity;
|
bool we_are_clicked_entity = entity->handle == game->clicked_entity;
|
||||||
if (action->state == FP_GameEntityState_Nil) {
|
bool action_has_finished = action->timer_s != FP_GAME_ENTITY_ACTION_INFINITE_TIMER && action->timer_s >= action->end_at_s;
|
||||||
action->state = FP_GameEntityState_Idle;
|
|
||||||
action->dirty = true;
|
if (action->state == FP_GameEntityState_Nil)
|
||||||
}
|
FP_Game_EntityActionSetState(action, FP_GameEntityState_Idle);
|
||||||
|
|
||||||
if (action->state == FP_GameEntityState_Idle) {
|
if (action->state == FP_GameEntityState_Idle) {
|
||||||
if (we_are_clicked_entity && TELY_Platform_InputScanCodeIsDown(input, TELY_PlatformInputScanCode_J)) {
|
if (action->flags & FP_GameEntityActionFlag_StateTransition) {
|
||||||
action->state = FP_GameEntityState_AttackA;
|
TELY_AssetSpriteAnimation *anim = entity->sprite_anims.data + TELY_Asset_GetSpriteAnimation(entity->sprite_anims, DQN_STRING8("Idle")).index;
|
||||||
action->dirty = true;
|
FP_Game_EntityActionReset(action, FP_GAME_ENTITY_ACTION_INFINITE_TIMER, anim);
|
||||||
} else if (we_are_clicked_entity && (dir_vector.x || dir_vector.y)) {
|
} else if (we_are_clicked_entity) {
|
||||||
action->state = FP_GameEntityState_Run;
|
if (TELY_Platform_InputScanCodeIsPressed(input, TELY_PlatformInputScanCode_J)) {
|
||||||
action->dirty = true;
|
FP_Game_EntityActionSetState(action, FP_GameEntityState_AttackA);
|
||||||
} else if (action->dirty) {
|
} else if (dir_vector.x || dir_vector.y) {
|
||||||
action->anim = {};
|
FP_Game_EntityActionSetState(action, FP_GameEntityState_Run);
|
||||||
action->anim.asset = entity->sprite_anims.data + TELY_Asset_GetSpriteAnimation(entity->sprite_anims, DQN_STRING8("Idle")).index;
|
}
|
||||||
action->timer_s = FP_GAME_ENTITY_ACTION_INFINITE_TIMER;
|
|
||||||
action->dirty = false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (action->state == FP_GameEntityState_AttackA) {
|
if (action->state == FP_GameEntityState_AttackA) {
|
||||||
if (action->dirty) {
|
if (action->flags & FP_GameEntityActionFlag_StateTransition) {
|
||||||
action->anim = {};
|
TELY_AssetSpriteAnimation *anim = entity->sprite_anims.data + TELY_Asset_GetSpriteAnimation(entity->sprite_anims, DQN_STRING8("Attack A")).index;
|
||||||
action->anim.asset = entity->sprite_anims.data + TELY_Asset_GetSpriteAnimation(entity->sprite_anims, DQN_STRING8("Attack A")).index;
|
FP_Game_EntityActionReset(action, anim->count * anim->seconds_per_frame, anim);
|
||||||
action->timer_s = action->anim.asset->count * 0.016f;
|
} else if (action_has_finished) {
|
||||||
action->dirty = false;
|
FP_Game_EntityActionSetState(action, FP_GameEntityState_Idle);
|
||||||
} else if (action->timer_s <= 0.f) {
|
} else if (!FP_Game_EntityActionHasFailed(action) && we_are_clicked_entity) {
|
||||||
action->state = FP_GameEntityState_Idle;
|
if (TELY_Platform_InputScanCodeIsPressed(input, TELY_PlatformInputScanCode_J)) {
|
||||||
action->dirty = true;
|
Dqn_f32 t01 = action->timer_s / action->end_at_s;
|
||||||
} else if (we_are_clicked_entity && TELY_Platform_InputScanCodeIsDown(input, TELY_PlatformInputScanCode_J)) {
|
if (t01 > 0.5f)
|
||||||
// NOTE: Trigger AttackB if action is at the correct time
|
FP_Game_EntityActionSetState(action, FP_GameEntityState_AttackB);
|
||||||
action->state = FP_GameEntityState_AttackB;
|
else
|
||||||
action->dirty = true;
|
action->flags |= FP_GameEntityActionFlag_Failed;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (action->state == FP_GameEntityState_AttackB) {
|
if (action->state == FP_GameEntityState_AttackB) {
|
||||||
if (action->dirty) {
|
if (action->flags & FP_GameEntityActionFlag_StateTransition) {
|
||||||
action->anim = {};
|
TELY_AssetSpriteAnimation *anim = entity->sprite_anims.data + TELY_Asset_GetSpriteAnimation(entity->sprite_anims, DQN_STRING8("Attack B")).index;
|
||||||
action->anim.asset = entity->sprite_anims.data + TELY_Asset_GetSpriteAnimation(entity->sprite_anims, DQN_STRING8("Attack B")).index;
|
FP_Game_EntityActionReset(action, anim->count * anim->seconds_per_frame, anim);
|
||||||
action->timer_s = action->anim.asset->count * 0.016f;
|
} else if (action_has_finished) {
|
||||||
action->dirty = false;
|
FP_Game_EntityActionSetState(action, FP_GameEntityState_Idle);
|
||||||
} else if (action->timer_s <= 0.f) {
|
} else if (!FP_Game_EntityActionHasFailed(action) && we_are_clicked_entity) {
|
||||||
action->state = FP_GameEntityState_Idle;
|
if (TELY_Platform_InputScanCodeIsPressed(input, TELY_PlatformInputScanCode_J)) {
|
||||||
action->dirty = true;
|
Dqn_f32 t01 = action->timer_s / action->end_at_s;
|
||||||
} else if (we_are_clicked_entity && TELY_Platform_InputScanCodeIsDown(input, TELY_PlatformInputScanCode_J)) {
|
if (t01 > 0.5f)
|
||||||
// NOTE: Trigger AttackC if action is at the correct time
|
FP_Game_EntityActionSetState(action, FP_GameEntityState_AttackC);
|
||||||
action->state = FP_GameEntityState_AttackC;
|
else
|
||||||
action->dirty = true;
|
action->flags |= FP_GameEntityActionFlag_Failed;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (action->state == FP_GameEntityState_AttackC) {
|
if (action->state == FP_GameEntityState_AttackC) {
|
||||||
if (action->dirty) {
|
if (action->flags & FP_GameEntityActionFlag_StateTransition) {
|
||||||
action->anim = {};
|
TELY_AssetSpriteAnimation *anim = entity->sprite_anims.data + TELY_Asset_GetSpriteAnimation(entity->sprite_anims, DQN_STRING8("Attack C")).index;
|
||||||
action->anim.asset = entity->sprite_anims.data + TELY_Asset_GetSpriteAnimation(entity->sprite_anims, DQN_STRING8("Attack C")).index;
|
FP_Game_EntityActionReset(action, anim->count * anim->seconds_per_frame, anim);
|
||||||
action->timer_s = action->anim.asset->count * 0.016f;
|
} else if (action_has_finished) {
|
||||||
action->dirty = false;
|
FP_Game_EntityActionSetState(action, FP_GameEntityState_Idle);
|
||||||
} else if (action->timer_s <= 0.f) {
|
|
||||||
action->state = FP_GameEntityState_Idle;
|
|
||||||
action->dirty = true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (action->state == FP_GameEntityState_Run) {
|
if (action->state == FP_GameEntityState_Run) {
|
||||||
if (action->dirty) {
|
if (action->flags & FP_GameEntityActionFlag_StateTransition) {
|
||||||
action->anim = {};
|
TELY_AssetSpriteAnimation *anim = entity->sprite_anims.data + TELY_Asset_GetSpriteAnimation(entity->sprite_anims, DQN_STRING8("Run")).index;
|
||||||
action->anim.asset = entity->sprite_anims.data + TELY_Asset_GetSpriteAnimation(entity->sprite_anims, DQN_STRING8("Run")).index;
|
FP_Game_EntityActionReset(action, FP_GAME_ENTITY_ACTION_INFINITE_TIMER, anim);
|
||||||
action->timer_s = FP_GAME_ENTITY_ACTION_INFINITE_TIMER;
|
} else if (we_are_clicked_entity) {
|
||||||
action->dirty = false;
|
if (TELY_Platform_InputScanCodeIsPressed(input, TELY_PlatformInputScanCode_J)) {
|
||||||
|
FP_Game_EntityActionSetState(action, FP_GameEntityState_AttackA);
|
||||||
|
} else if (dir_vector.x == 0.f && dir_vector.y == 0.f) {
|
||||||
|
FP_Game_EntityActionSetState(action, FP_GameEntityState_Idle);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (we_are_clicked_entity && (dir_vector.x == 0.f && dir_vector.y == 0.f)) {
|
|
||||||
action->state = FP_GameEntityState_Idle;
|
|
||||||
action->dirty = true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// NOTE: Tick entity action ================================================================
|
// NOTE: Tick entity action ================================================================
|
||||||
TELY_AssetSpriteAnimation const *anim = action->anim.asset;
|
action->timer_s += DQN_CAST(Dqn_f32)input->delta_s;
|
||||||
if (anim) {
|
|
||||||
if (action->anim.frame >= anim->count) { // NOTE: Animation is finished
|
|
||||||
action->anim.frame = 0;
|
|
||||||
action->anim.ticks = 0;
|
|
||||||
} else if (action->anim.ticks++ > 4 /*ticks_per_anim_frame*/) {
|
|
||||||
action->anim.frame++;
|
|
||||||
action->anim.ticks = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (action->timer_s != FP_GAME_ENTITY_ACTION_INFINITE_TIMER) {
|
|
||||||
action->timer_s = DQN_MAX(action->timer_s - DQN_CAST(Dqn_f32)input->delta_s, 0.f);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NOTE: Calculate entity attack box =======================================================
|
// NOTE: Calculate entity attack box =======================================================
|
||||||
if (action->state == FP_GameEntityState_AttackA) {
|
if (action->state == FP_GameEntityState_AttackA ||
|
||||||
|
action->state == FP_GameEntityState_AttackB ||
|
||||||
|
action->state == FP_GameEntityState_AttackC) {
|
||||||
entity->attack_box_size = entity->local_hit_box_size;
|
entity->attack_box_size = entity->local_hit_box_size;
|
||||||
TELY_AssetSpriteSheet const *sprite_sheet = entity->sprite_sheet;
|
TELY_AssetSpriteSheet const *sprite_sheet = entity->sprite_sheet;
|
||||||
if (sprite_sheet) {
|
if (sprite_sheet) {
|
||||||
@ -730,11 +714,14 @@ void FP_GameRender(FP_Game *game, TELY_Platform *platform, TELY_Renderer *render
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NOTE: Render entity sprites =============================================================
|
// NOTE: Render entity sprites =============================================================
|
||||||
if (entity->sprite_sheet && entity->action.anim.asset) {
|
if (entity->sprite_sheet && entity->action.anim) {
|
||||||
TELY_AssetSpriteSheet const *sprite_sheet = entity->sprite_sheet;
|
|
||||||
TELY_AssetSpriteAnimation const *sprite_anim = entity->action.anim.asset;
|
|
||||||
|
|
||||||
Dqn_usize sprite_index = (sprite_anim->index + (entity->action.anim.frame % sprite_anim->count)) % sprite_sheet->sprite_count;
|
TELY_AssetSpriteSheet const *sprite_sheet = entity->sprite_sheet;
|
||||||
|
FP_GameEntityAction const *action = &entity->action;
|
||||||
|
TELY_AssetSpriteAnimation const *sprite_anim = action->anim;
|
||||||
|
uint16_t anim_frame = DQN_CAST(uint16_t)(action->timer_s / sprite_anim->seconds_per_frame) % sprite_anim->count;
|
||||||
|
|
||||||
|
Dqn_usize sprite_index = sprite_anim->index + anim_frame;
|
||||||
Dqn_usize sprite_sheet_row = sprite_index / sprite_sheet->sprites_per_row;
|
Dqn_usize sprite_sheet_row = sprite_index / sprite_sheet->sprites_per_row;
|
||||||
Dqn_usize sprite_sheet_column = sprite_index % sprite_sheet->sprites_per_row;
|
Dqn_usize sprite_sheet_column = sprite_index % sprite_sheet->sprites_per_row;
|
||||||
|
|
||||||
|
19
feely_pona.h
19
feely_pona.h
@ -52,14 +52,6 @@ enum FP_GameEntityState
|
|||||||
FP_GameEntityState_Run,
|
FP_GameEntityState_Run,
|
||||||
};
|
};
|
||||||
|
|
||||||
struct FP_GameEntityAnimation
|
|
||||||
{
|
|
||||||
TELY_AssetSpriteAnimation *asset;
|
|
||||||
uint16_t frame;
|
|
||||||
uint16_t ticks;
|
|
||||||
uint16_t ticks_per_frame;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct FP_GameWaypoint
|
struct FP_GameWaypoint
|
||||||
{
|
{
|
||||||
Dqn_V2I pos;
|
Dqn_V2I pos;
|
||||||
@ -67,13 +59,20 @@ struct FP_GameWaypoint
|
|||||||
FP_GameWaypoint *prev;
|
FP_GameWaypoint *prev;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum FP_GameEntityActionFlag
|
||||||
|
{
|
||||||
|
FP_GameEntityActionFlag_StateTransition = 1 << 0,
|
||||||
|
FP_GameEntityActionFlag_Failed = 1 << 1,
|
||||||
|
};
|
||||||
|
|
||||||
Dqn_f32 const FP_GAME_ENTITY_ACTION_INFINITE_TIMER = -1.f;
|
Dqn_f32 const FP_GAME_ENTITY_ACTION_INFINITE_TIMER = -1.f;
|
||||||
struct FP_GameEntityAction
|
struct FP_GameEntityAction
|
||||||
{
|
{
|
||||||
FP_GameEntityState state;
|
FP_GameEntityState state;
|
||||||
bool dirty;
|
uint32_t flags; // Bit flags corresponding with `FP_GameEntityActionFlag`
|
||||||
FP_GameEntityAnimation anim;
|
TELY_AssetSpriteAnimation *anim;
|
||||||
Dqn_f32 timer_s;
|
Dqn_f32 timer_s;
|
||||||
|
Dqn_f32 end_at_s;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct FP_GameEntity
|
struct FP_GameEntity
|
||||||
|
@ -421,3 +421,31 @@ static Dqn_Rect FP_Game_CalcEntityWorldBoundingBox(FP_Game *game, FP_GameEntityH
|
|||||||
Dqn_Rect result = FP_Game_CalcEntityArrayWorldBoundingBox(game, &handle, 1);
|
Dqn_Rect result = FP_Game_CalcEntityArrayWorldBoundingBox(game, &handle, 1);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Transition the action into the desire state and set the flag to indicate it
|
||||||
|
// has just transitioned
|
||||||
|
static void FP_Game_EntityActionSetState(FP_GameEntityAction *action, FP_GameEntityState state)
|
||||||
|
{
|
||||||
|
if (!action)
|
||||||
|
return;
|
||||||
|
action->state = state;
|
||||||
|
action->flags |= FP_GameEntityActionFlag_StateTransition;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset the timers and animation for the current action and set the duration
|
||||||
|
// for the new action.
|
||||||
|
static void FP_Game_EntityActionReset(FP_GameEntityAction *action, Dqn_f32 new_action_duration, TELY_AssetSpriteAnimation *anim)
|
||||||
|
{
|
||||||
|
if (!action)
|
||||||
|
return;
|
||||||
|
action->anim = anim;
|
||||||
|
action->timer_s = 0.f;
|
||||||
|
action->end_at_s = new_action_duration;
|
||||||
|
action->flags = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool FP_Game_EntityActionHasFailed(FP_GameEntityAction const *action)
|
||||||
|
{
|
||||||
|
bool result = action ? action->flags & FP_GameEntityActionFlag_Failed : true;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user