diff --git a/External/tely b/External/tely index 92ae772..96ddc4d 160000 --- a/External/tely +++ b/External/tely @@ -1 +1 @@ -Subproject commit 92ae772b6fa8f79bb0d5a93b643305fe482a6ed3 +Subproject commit 96ddc4d93441f1fdccf04d9f7faed96de7971c52 diff --git a/feely_pona.cpp b/feely_pona.cpp index b4bd250..171beff 100644 --- a/feely_pona.cpp +++ b/feely_pona.cpp @@ -83,7 +83,8 @@ TELY_AssetSpriteSheet FP_LoadSpriteSheetFromSpec(TELY_Platform *platform, TELY_A anim->label = Dqn_String8_Copy(allocator, anim_name); anim->index = DQN_CAST(uint16_t)sprite_rect_index; anim->count = DQN_CAST(uint16_t)frame_count.value; - anim->seconds_per_frame = 1.f / frames_per_second.value; + anim->ms_per_frame = DQN_CAST(uint32_t)(1000.f / frames_per_second.value); + DQN_ASSERT(anim->ms_per_frame != 0); } else { DQN_ASSERTF(line_splits.size == 4, "Expected 4 splits for sprite frame lines"); Dqn_String8ToU64Result x = Dqn_String8_ToU64(line_splits.data[0], 0); @@ -140,26 +141,26 @@ void TELY_DLL_Init(void *user_data) sheet->sprite_size = Dqn_V2I_InitNx2(50, 37); TELY_AssetSpriteAnimation hero_anims[] = { - {DQN_STRING8("Everything"), /*index*/ 0, /*count*/ sheet->sprite_count, /*seconds_per_frame*/ 1 / 12.f}, - {DQN_STRING8("Idle"), /*index*/ 0, /*count*/ 3, /*seconds_per_frame*/ 1 / 4.f}, - {DQN_STRING8("Run"), /*index*/ 8, /*count*/ 6, /*seconds_per_frame*/ 1 / 8.f}, - {DQN_STRING8("Jump"), /*index*/ 14, /*count*/ 10, /*seconds_per_frame*/ 1 / 12.f}, - {DQN_STRING8("Floor slide"), /*index*/ 24, /*count*/ 5, /*seconds_per_frame*/ 1 / 12.f}, - {DQN_STRING8("Unknown"), /*index*/ 29, /*count*/ 9, /*seconds_per_frame*/ 1 / 12.f}, - {DQN_STRING8("Attack A"), /*index*/ 42, /*count*/ 7, /*seconds_per_frame*/ 1 / 12.f}, - {DQN_STRING8("Attack B"), /*index*/ 49, /*count*/ 4, /*seconds_per_frame*/ 1 / 8.f}, - {DQN_STRING8("Attack C"), /*index*/ 53, /*count*/ 6, /*seconds_per_frame*/ 1 / 12.f}, - {DQN_STRING8("Hurt A"), /*index*/ 59, /*count*/ 5, /*seconds_per_frame*/ 1 / 12.f}, - {DQN_STRING8("Hurt B"), /*index*/ 64, /*count*/ 5, /*seconds_per_frame*/ 1 / 12.f}, - {DQN_STRING8("Unsheath sword"), /*index*/ 69, /*count*/ 4, /*seconds_per_frame*/ 1 / 12.f}, - {DQN_STRING8("Sheath sword"), /*index*/ 73, /*count*/ 4, /*seconds_per_frame*/ 1 / 12.f}, - {DQN_STRING8("Air drift"), /*index*/ 77, /*count*/ 2, /*seconds_per_frame*/ 1 / 12.f}, - {DQN_STRING8("Air drop"), /*index*/ 79, /*count*/ 2, /*seconds_per_frame*/ 1 / 12.f}, - {DQN_STRING8("Ladder climb"), /*index*/ 81, /*count*/ 4, /*seconds_per_frame*/ 1 / 12.f}, - {DQN_STRING8("Chi push"), /*index*/ 85, /*count*/ 8, /*seconds_per_frame*/ 1 / 12.f}, - {DQN_STRING8("Leap slice A"), /*index*/ 93, /*count*/ 7, /*seconds_per_frame*/ 1 / 12.f}, - {DQN_STRING8("Leap slice B"), /*index*/ 100, /*count*/ 3, /*seconds_per_frame*/ 1 / 12.f}, - {DQN_STRING8("Leap slice C"), /*index*/ 103, /*count*/ 6, /*seconds_per_frame*/ 1 / 12.f}, + {DQN_STRING8("Everything"), /*index*/ 0, /*count*/ sheet->sprite_count, /*ms_per_frame*/ DQN_CAST(uint32_t)(1000.f / 12.f)}, + {DQN_STRING8("Idle"), /*index*/ 0, /*count*/ 3, /*ms_per_frame*/ DQN_CAST(uint32_t)(1000.f / 4.f)}, + {DQN_STRING8("Run"), /*index*/ 8, /*count*/ 6, /*ms_per_frame*/ DQN_CAST(uint32_t)(1000.f / 8.f)}, + {DQN_STRING8("Jump"), /*index*/ 14, /*count*/ 10, /*ms_per_frame*/ DQN_CAST(uint32_t)(1000.f / 12.f)}, + {DQN_STRING8("Floor slide"), /*index*/ 24, /*count*/ 5, /*ms_per_frame*/ DQN_CAST(uint32_t)(1000.f / 12.f)}, + {DQN_STRING8("Unknown"), /*index*/ 29, /*count*/ 9, /*ms_per_frame*/ DQN_CAST(uint32_t)(1000.f / 12.f)}, + {DQN_STRING8("Attack A"), /*index*/ 42, /*count*/ 7, /*ms_per_frame*/ DQN_CAST(uint32_t)(1000.f / 12.f)}, + {DQN_STRING8("Attack B"), /*index*/ 49, /*count*/ 4, /*ms_per_frame*/ DQN_CAST(uint32_t)(1000.f / 8.f)}, + {DQN_STRING8("Attack C"), /*index*/ 53, /*count*/ 6, /*ms_per_frame*/ DQN_CAST(uint32_t)(1000.f / 12.f)}, + {DQN_STRING8("Hurt A"), /*index*/ 59, /*count*/ 5, /*ms_per_frame*/ DQN_CAST(uint32_t)(1000.f / 12.f)}, + {DQN_STRING8("Hurt B"), /*index*/ 64, /*count*/ 5, /*ms_per_frame*/ DQN_CAST(uint32_t)(1000.f / 12.f)}, + {DQN_STRING8("Unsheath sword"), /*index*/ 69, /*count*/ 4, /*ms_per_frame*/ DQN_CAST(uint32_t)(1000.f / 12.f)}, + {DQN_STRING8("Sheath sword"), /*index*/ 73, /*count*/ 4, /*ms_per_frame*/ DQN_CAST(uint32_t)(1000.f / 12.f)}, + {DQN_STRING8("Air drift"), /*index*/ 77, /*count*/ 2, /*ms_per_frame*/ DQN_CAST(uint32_t)(1000.f / 12.f)}, + {DQN_STRING8("Air drop"), /*index*/ 79, /*count*/ 2, /*ms_per_frame*/ DQN_CAST(uint32_t)(1000.f / 12.f)}, + {DQN_STRING8("Ladder climb"), /*index*/ 81, /*count*/ 4, /*ms_per_frame*/ DQN_CAST(uint32_t)(1000.f / 12.f)}, + {DQN_STRING8("Chi push"), /*index*/ 85, /*count*/ 8, /*ms_per_frame*/ DQN_CAST(uint32_t)(1000.f / 12.f)}, + {DQN_STRING8("Leap slice A"), /*index*/ 93, /*count*/ 7, /*ms_per_frame*/ DQN_CAST(uint32_t)(1000.f / 12.f)}, + {DQN_STRING8("Leap slice B"), /*index*/ 100, /*count*/ 3, /*ms_per_frame*/ DQN_CAST(uint32_t)(1000.f / 12.f)}, + {DQN_STRING8("Leap slice C"), /*index*/ 103, /*count*/ 6, /*ms_per_frame*/ DQN_CAST(uint32_t)(1000.f / 12.f)}, }; game->hero_sprite_anims = Dqn_Slice_Alloc(&platform->arena, DQN_ARRAY_UCOUNT(hero_anims), Dqn_ZeroMem_No); @@ -291,13 +292,14 @@ void TELY_DLL_Init(void *user_data) game->test_audio = platform->func_load_audio(assets, DQN_STRING8("Test Audio"), DQN_STRING8("Data/Audio/Purrple Cat - Moonwinds.qoa")); } -FP_GameEntityActionSprite FP_EntityActionStateMachine(FP_Game *game, TELY_PlatformInput *input, FP_GameEntity *entity, Dqn_V2 dir_vector) +void FP_EntityActionStateMachine(FP_Game *game, TELY_PlatformInput *input, FP_GameEntity *entity, Dqn_V2 dir_vector) { FP_GameEntityAction *action = &entity->action; - bool we_are_clicked_entity = entity->handle == game->clicked_entity; - bool action_has_finished = action->timer_s != FP_GAME_ENTITY_ACTION_INFINITE_TIMER && action->timer_s >= action->end_at_s; - bool entity_has_velocity = entity->velocity.x || entity->velocity.y; - FP_GameEntityActionSprite result = {}; + bool const we_are_clicked_entity = entity->handle == game->clicked_entity; + bool const entity_has_velocity = entity->velocity.x || entity->velocity.y; + bool const entering_new_state = action->state != action->next_state; + bool const action_has_finished = !entering_new_state && game->clock_ms >= action->end_at_clock_ms; + action->state = action->next_state; switch (entity->type) { case FP_EntityType_Nil: { @@ -306,84 +308,78 @@ FP_GameEntityActionSprite FP_EntityActionStateMachine(FP_Game *game, TELY_Platfo case FP_EntityType_Terry: { FP_EntityTerryState *state = DQN_CAST(FP_EntityTerryState *) & action->state; TELY_AssetSpriteSheet *sheet = &game->terry_sprite_sheet; - result.sheet = sheet; switch (*state) { case FP_EntityTerryState_Nil: { - FP_Game_EntityActionSetState(action, FP_EntityTerryState_Idle); + action->next_state = FP_EntityTerryState_Idle; } break; case FP_EntityTerryState_Idle: { - result.anim = TELY_Asset_GetSpriteAnimation(sheet, g_anim_names.terry_walk_idle); - if (action->flags & FP_GameEntityActionFlag_StateTransition) { - FP_Game_EntityActionReset(action, FP_GAME_ENTITY_ACTION_INFINITE_TIMER, result); - } else if (we_are_clicked_entity) { + if (entering_new_state) { + TELY_AssetAnimatedSprite sprite = TELY_Asset_MakeAnimatedSprite(sheet, g_anim_names.terry_walk_idle, TELY_AssetFlip_No); + uint64_t duration_ms = FP_GAME_ENTITY_ACTION_INFINITE_TIMER; + FP_Game_EntityActionReset(game, entity->handle, duration_ms, sprite); + } + + if (we_are_clicked_entity) { if (TELY_Platform_InputScanCodeIsPressed(input, TELY_PlatformInputScanCode_J) || TELY_Platform_InputGamepadButtonCodeIsPressed(input, 0, TELY_PlatformInputGamepadButtonCode_X)) { switch (entity->direction) { case FP_GameDirection_Up: { - FP_Game_EntityActionSetState(action, FP_EntityTerryState_AttackUp); + action->next_state = FP_EntityTerryState_AttackUp; } break; case FP_GameDirection_Left: { - FP_Game_EntityActionSetState(action, FP_EntityTerryState_AttackSide); + action->next_state = FP_EntityTerryState_AttackSide; } break; case FP_GameDirection_Right: { - FP_Game_EntityActionSetState(action, FP_EntityTerryState_AttackSide); + action->next_state = FP_EntityTerryState_AttackSide; } break; case FP_GameDirection_Down: { - FP_Game_EntityActionSetState(action, FP_EntityTerryState_AttackDown); + action->next_state = FP_EntityTerryState_AttackDown; } break; } } else if (dir_vector.x || dir_vector.y) { - FP_Game_EntityActionSetState(action, FP_EntityTerryState_Run); + action->next_state = FP_EntityTerryState_Run; } } } break; case FP_EntityTerryState_AttackUp: { - result.anim = TELY_Asset_GetSpriteAnimation(sheet, g_anim_names.terry_attack_up); - if (action->flags & FP_GameEntityActionFlag_StateTransition) { - FP_Game_EntityActionReset(action, result.anim->count * result.anim->seconds_per_frame, result); - } else if (action_has_finished) { - FP_Game_EntityActionSetState(action, FP_EntityTerryState_Idle); + if (entering_new_state) { + TELY_AssetAnimatedSprite sprite = TELY_Asset_MakeAnimatedSprite(sheet, g_anim_names.terry_attack_up, TELY_AssetFlip_No); + uint64_t duration_ms = sprite.anim->count * sprite.anim->ms_per_frame; + FP_Game_EntityActionReset(game, entity->handle, duration_ms, sprite); + } + + if (action_has_finished) { + action->next_state = FP_EntityTerryState_Idle; } } break; case FP_EntityTerryState_AttackDown: { - result.anim = TELY_Asset_GetSpriteAnimation(sheet, g_anim_names.terry_walk_down); - if (action->flags & FP_GameEntityActionFlag_StateTransition) { - FP_Game_EntityActionReset(action, result.anim->count * result.anim->seconds_per_frame, result); - } else if (action_has_finished) { - FP_Game_EntityActionSetState(action, FP_EntityTerryState_Idle); + if (entering_new_state) { + TELY_AssetAnimatedSprite sprite = TELY_Asset_MakeAnimatedSprite(sheet, g_anim_names.terry_attack_down, TELY_AssetFlip_No); + uint64_t duration_ms = sprite.anim->count * sprite.anim->ms_per_frame; + FP_Game_EntityActionReset(game, entity->handle, duration_ms, sprite); } + + if (action_has_finished) + action->next_state = FP_EntityTerryState_Idle; } break; case FP_EntityTerryState_AttackSide: { - result.anim = TELY_Asset_GetSpriteAnimation(sheet, g_anim_names.terry_attack_side); - action->flip_on_x = entity->direction == FP_GameDirection_Right; - - if (action->flags & FP_GameEntityActionFlag_StateTransition) { - FP_Game_EntityActionReset(action, result.anim->count * result.anim->seconds_per_frame, result); - } else if (action_has_finished) { - FP_Game_EntityActionSetState(action, FP_EntityTerryState_Idle); - action->flip_on_x = false; + if (entering_new_state) { + TELY_AssetFlip flip = entity->direction == FP_GameDirection_Right ? TELY_AssetFlip_X : TELY_AssetFlip_No; + TELY_AssetAnimatedSprite sprite = TELY_Asset_MakeAnimatedSprite(sheet, g_anim_names.terry_attack_side, flip); + uint64_t duration_ms = sprite.anim->count * sprite.anim->ms_per_frame; + FP_Game_EntityActionReset(game, entity->handle, duration_ms, sprite); } - #if 0 - else if (!FP_Game_EntityActionHasFailed(action) && we_are_clicked_entity) { - if (TELY_Platform_InputScanCodeIsPressed(input, TELY_PlatformInputScanCode_J) || - TELY_Platform_InputGamepadButtonCodeIsPressed(input, 0, TELY_PlatformInputGamepadButtonCode_X)) { - Dqn_f32 t01 = action->timer_s / action->end_at_s; - if (t01 > 0.5f) - FP_Game_EntityActionSetState(action, FP_EntityTerryState_AttackB); - else - action->flags |= FP_GameEntityActionFlag_Failed; - } - } - #endif + if (action_has_finished) + action->next_state = FP_EntityTerryState_Idle; } break; case FP_EntityTerryState_Run: { @@ -394,49 +390,50 @@ FP_GameEntityActionSprite FP_EntityActionStateMachine(FP_Game *game, TELY_Platfo case FP_GameDirection_Left: desired_action_name = g_anim_names.terry_walk_left; break; case FP_GameDirection_Right: desired_action_name = g_anim_names.terry_walk_right; break; } - result.anim = TELY_Asset_GetSpriteAnimation(sheet, desired_action_name); + + if (entering_new_state || action->sprite.anim->label != desired_action_name) { + TELY_AssetAnimatedSprite sprite = TELY_Asset_MakeAnimatedSprite(sheet, desired_action_name, TELY_AssetFlip_No); + uint64_t duration_ms = FP_GAME_ENTITY_ACTION_INFINITE_TIMER; + FP_Game_EntityActionReset(game, entity->handle, duration_ms, sprite); + } if (we_are_clicked_entity) { if (TELY_Platform_InputScanCodeIsPressed(input, TELY_PlatformInputScanCode_J) || TELY_Platform_InputGamepadButtonCodeIsPressed(input, 0, TELY_PlatformInputGamepadButtonCode_X)) { switch (entity->direction) { case FP_GameDirection_Up: { - FP_Game_EntityActionSetState(action, FP_EntityTerryState_AttackUp); + action->next_state = FP_EntityTerryState_AttackUp; } break; case FP_GameDirection_Left: { - FP_Game_EntityActionSetState(action, FP_EntityTerryState_AttackSide); + action->next_state = FP_EntityTerryState_AttackSide; } break; case FP_GameDirection_Right: { - FP_Game_EntityActionSetState(action, FP_EntityTerryState_AttackSide); + action->next_state = FP_EntityTerryState_AttackSide; } break; case FP_GameDirection_Down: { - FP_Game_EntityActionSetState(action, FP_EntityTerryState_AttackDown); + action->next_state = FP_EntityTerryState_AttackDown; } break; } } else if (TELY_Platform_InputScanCodeIsPressed(input, TELY_PlatformInputScanCode_LeftShift) || TELY_Platform_InputGamepadButtonCodeIsPressed(input, 0, TELY_PlatformInputGamepadButtonCode_A)) { - FP_Game_EntityActionSetState(action, FP_EntityTerryState_Dash); + action->next_state = FP_EntityTerryState_Dash; } } - // NOTE: Also handles state transition - if (action->sprite.anim->label != result.anim->label) { - FP_Game_EntityActionReset(action, FP_GAME_ENTITY_ACTION_INFINITE_TIMER, result); - } - if (!entity_has_velocity /*&& !has_collision*/) { - FP_Game_EntityActionSetState(action, FP_EntityTerryState_Idle); + action->next_state = FP_EntityTerryState_Idle; } } break; case FP_EntityTerryState_Dash: { - result.anim = TELY_Asset_GetSpriteAnimation(sheet, g_anim_names.terry_walk_right); - if (action->flags & FP_GameEntityActionFlag_StateTransition) { - FP_Game_EntityActionReset(action, result.anim->count * result.anim->seconds_per_frame, result); + if (entering_new_state) { + TELY_AssetAnimatedSprite sprite = TELY_Asset_MakeAnimatedSprite(sheet, g_anim_names.terry_walk_right, TELY_AssetFlip_No); + uint64_t duration_ms = sprite.anim->count * sprite.anim->ms_per_frame; + FP_Game_EntityActionReset(game, entity->handle, duration_ms, sprite); Dqn_V2 dash_dir = {}; switch (entity->direction) { @@ -449,13 +446,14 @@ FP_GameEntityActionSprite FP_EntityActionStateMachine(FP_Game *game, TELY_Platfo Dqn_V2 dash_acceleration = dash_dir * 400'000'000.f; Dqn_f32 t = DQN_CAST(Dqn_f32)DQN_SQUARED(input->delta_s); entity->velocity = (dash_acceleration * t) + entity->velocity * 2.0f; + } - } else if (action_has_finished) { + if (action_has_finished) { if (entity_has_velocity) { // TODO(doyle): Not sure if this branch triggers properly. - FP_Game_EntityActionSetState(action, FP_EntityTerryState_Run); + action->next_state = FP_EntityTerryState_Run; } else { - FP_Game_EntityActionSetState(action, FP_EntityTerryState_Idle); + action->next_state = FP_EntityTerryState_Idle; } } } break; @@ -482,19 +480,21 @@ FP_GameEntityActionSprite FP_EntityActionStateMachine(FP_Game *game, TELY_Platfo case FP_EntityType_Smoochie: { FP_EntitySmoochieState *state = DQN_CAST(FP_EntitySmoochieState *) & action->state; TELY_AssetSpriteSheet *sheet = &game->smoochie_sprite_sheet; - result.sheet = sheet; switch (*state) { case FP_EntitySmoochieState_Nil: { - FP_Game_EntityActionSetState(action, FP_EntitySmoochieState_Idle); + action->next_state = FP_EntitySmoochieState_Idle; } break; case FP_EntitySmoochieState_Idle: { - result.anim = TELY_Asset_GetSpriteAnimation(sheet, g_anim_names.smoochie_walk_down); - if (action->flags & FP_GameEntityActionFlag_StateTransition) { - FP_Game_EntityActionReset(action, FP_GAME_ENTITY_ACTION_INFINITE_TIMER, result); - } else if (we_are_clicked_entity) { + if (entering_new_state) { + TELY_AssetAnimatedSprite sprite = TELY_Asset_MakeAnimatedSprite(sheet, g_anim_names.smoochie_walk_down, TELY_AssetFlip_No); + uint64_t duration_ms = FP_GAME_ENTITY_ACTION_INFINITE_TIMER; + FP_Game_EntityActionReset(game, entity->handle, duration_ms, sprite); + } + + if (we_are_clicked_entity) { if (TELY_Platform_InputScanCodeIsPressed(input, TELY_PlatformInputScanCode_J) || TELY_Platform_InputGamepadButtonCodeIsPressed(input, 0, TELY_PlatformInputGamepadButtonCode_X)) { @@ -502,37 +502,41 @@ FP_GameEntityActionSprite FP_EntityActionStateMachine(FP_Game *game, TELY_Platfo case FP_GameDirection_Up: /*FALLTHRU*/ case FP_GameDirection_Right: /*FALLTHRU*/ case FP_GameDirection_Left: { - FP_Game_EntityActionSetState(action, FP_EntitySmoochieState_AttackSide); + action->next_state = FP_EntitySmoochieState_AttackSide; } break; case FP_GameDirection_Down: { - FP_Game_EntityActionSetState(action, FP_EntitySmoochieState_AttackDown); + action->next_state = FP_EntitySmoochieState_AttackDown; } break; } } else if (dir_vector.x || dir_vector.y) { - FP_Game_EntityActionSetState(action, FP_EntitySmoochieState_Run); + action->next_state = FP_EntitySmoochieState_Run; } } } break; case FP_EntitySmoochieState_AttackDown: { - result.anim = TELY_Asset_GetSpriteAnimation(sheet, g_anim_names.smoochie_attack_down); - if (action->flags & FP_GameEntityActionFlag_StateTransition) { - FP_Game_EntityActionReset(action, result.anim->count * result.anim->seconds_per_frame, result); - } else if (action_has_finished) { - FP_Game_EntityActionSetState(action, FP_EntitySmoochieState_Idle); + if (entering_new_state) { + TELY_AssetAnimatedSprite sprite = TELY_Asset_MakeAnimatedSprite(sheet, g_anim_names.smoochie_attack_down, TELY_AssetFlip_No); + uint64_t duration_ms = sprite.anim->count * sprite.anim->ms_per_frame; + FP_Game_EntityActionReset(game, entity->handle, duration_ms, sprite); + } + + if (action_has_finished) { + action->next_state = FP_EntitySmoochieState_Idle; } } break; case FP_EntitySmoochieState_AttackSide: { - result.anim = TELY_Asset_GetSpriteAnimation(sheet, g_anim_names.smoochie_attack_side); - action->flip_on_x = entity->direction == FP_GameDirection_Right; - if (action->flags & FP_GameEntityActionFlag_StateTransition) { - FP_Game_EntityActionReset(action, result.anim->count * result.anim->seconds_per_frame, result); - } else if (action_has_finished) { - FP_Game_EntityActionSetState(action, FP_EntitySmoochieState_Idle); - action->flip_on_x = false; + if (entering_new_state) { + TELY_AssetFlip flip = entity->direction == FP_GameDirection_Right ? TELY_AssetFlip_X : TELY_AssetFlip_No; + TELY_AssetAnimatedSprite sprite = TELY_Asset_MakeAnimatedSprite(sheet, g_anim_names.smoochie_attack_side, flip); + uint64_t duration_ms = sprite.anim->count * sprite.anim->ms_per_frame; + FP_Game_EntityActionReset(game, entity->handle, duration_ms, sprite); } + + if (action_has_finished) + action->next_state = FP_EntitySmoochieState_Idle; } break; case FP_EntitySmoochieState_AttackHeart: { @@ -546,7 +550,12 @@ FP_GameEntityActionSprite FP_EntityActionStateMachine(FP_Game *game, TELY_Platfo case FP_GameDirection_Left: desired_action_name = g_anim_names.smoochie_walk_left; break; case FP_GameDirection_Right: desired_action_name = g_anim_names.smoochie_walk_right; break; } - result.anim = TELY_Asset_GetSpriteAnimation(sheet, desired_action_name); + + if (entering_new_state || action->sprite.anim->label != desired_action_name) { + TELY_AssetAnimatedSprite sprite = TELY_Asset_MakeAnimatedSprite(sheet, desired_action_name, TELY_AssetFlip_No); + uint64_t duration_ms = FP_GAME_ENTITY_ACTION_INFINITE_TIMER; + FP_Game_EntityActionReset(game, entity->handle, duration_ms, sprite); + } if (we_are_clicked_entity) { if (TELY_Platform_InputScanCodeIsPressed(input, TELY_PlatformInputScanCode_J) || @@ -556,23 +565,18 @@ FP_GameEntityActionSprite FP_EntityActionStateMachine(FP_Game *game, TELY_Platfo case FP_GameDirection_Up: /*FALLTHRU*/ case FP_GameDirection_Right: /*FALLTHRU*/ case FP_GameDirection_Left: { - FP_Game_EntityActionSetState(action, FP_EntitySmoochieState_AttackSide); + action->next_state = FP_EntitySmoochieState_AttackSide; } break; case FP_GameDirection_Down: { - FP_Game_EntityActionSetState(action, FP_EntitySmoochieState_AttackDown); + action->next_state = FP_EntitySmoochieState_AttackDown; } break; } } } - // NOTE: Also handles state transition - if (action->sprite.anim->label != result.anim->label) { - FP_Game_EntityActionReset(action, FP_GAME_ENTITY_ACTION_INFINITE_TIMER, result); - } - if (!entity_has_velocity /*&& !has_collision*/) { - FP_Game_EntityActionSetState(action, FP_EntitySmoochieState_Idle); + action->next_state = FP_EntitySmoochieState_Idle; } } break; } @@ -595,26 +599,23 @@ FP_GameEntityActionSprite FP_EntityActionStateMachine(FP_Game *game, TELY_Platfo case FP_EntityType_Merchant: { FP_EntityTerryMerchantState *state = DQN_CAST(FP_EntityTerryMerchantState *)&action->state; TELY_AssetSpriteSheet *sheet = &game->terry_merchant_sprite_sheet; - result.sheet = sheet; switch (*state) { case FP_EntityTerryMerchantState_Nil: { - FP_Game_EntityActionSetState(action, FP_EntityTerryMerchantState_Idle); + action->next_state = FP_EntityTerryMerchantState_Idle; } break; case FP_EntityTerryMerchantState_Idle: { - result.anim = TELY_Asset_GetSpriteAnimation(sheet, g_anim_names.terry_merchant); - if (action->flags & FP_GameEntityActionFlag_StateTransition) { - FP_Game_EntityActionReset(action, FP_GAME_ENTITY_ACTION_INFINITE_TIMER, result); + if (entering_new_state) { + TELY_AssetAnimatedSprite sprite = TELY_Asset_MakeAnimatedSprite(sheet, g_anim_names.terry_merchant, TELY_AssetFlip_No); + uint64_t duration_ms = FP_GAME_ENTITY_ACTION_INFINITE_TIMER; + FP_Game_EntityActionReset(game, entity->handle, duration_ms, sprite); } + } break; } } break; } - - // NOTE: Tick entity action ================================================================ - action->timer_s += DQN_CAST(Dqn_f32)input->delta_s; - return result; } void FP_Update(TELY_Platform *platform, FP_Game *game, TELY_Renderer *renderer, TELY_PlatformInput *input) @@ -624,6 +625,7 @@ void FP_Update(TELY_Platform *platform, FP_Game *game, TELY_Renderer *renderer, if (TELY_Platform_InputKeyIsReleased(input->mouse_left)) game->clicked_entity = game->prev_active_entity; + game->clock_ms = DQN_CAST(uint64_t)(platform->input.timer_s * 1000.f); Dqn_V2 dir_vector = {}; // NOTE: Keyboard movement input @@ -1019,26 +1021,27 @@ void FP_Render(FP_Game *game, TELY_Platform *platform, TELY_Renderer *renderer) // NOTE: Render entity sprites ============================================================= if (entity->action.sprite.anim) { - FP_GameEntityAction const *action = &entity->action; - TELY_AssetSpriteSheet const *sprite_sheet = action->sprite.sheet; - TELY_AssetSpriteAnimation const *sprite_anim = action->sprite.anim; - uint16_t anim_frame = DQN_CAST(uint16_t)(action->timer_s / sprite_anim->seconds_per_frame) % sprite_anim->count; + FP_GameEntityAction const *action = &entity->action; + TELY_AssetAnimatedSprite const sprite = action->sprite; - Dqn_usize sprite_index = sprite_anim->index + anim_frame; + uint64_t elapsed_ms = game->clock_ms - action->started_at_clock_ms; + uint16_t anim_frame = DQN_CAST(uint16_t)(elapsed_ms / sprite.anim->ms_per_frame) % sprite.anim->count; + + Dqn_usize sprite_index = sprite.anim->index + anim_frame; Dqn_Rect src_rect = {}; - switch (sprite_sheet->type) { + switch (sprite.sheet->type) { case TELY_AssetSpriteSheetType_Uniform: { - Dqn_usize sprite_sheet_row = sprite_index / sprite_sheet->sprites_per_row; - Dqn_usize sprite_sheet_column = sprite_index % sprite_sheet->sprites_per_row; - src_rect.pos.x = DQN_CAST(Dqn_f32)(sprite_sheet_column * sprite_sheet->sprite_size.w); - src_rect.pos.y = DQN_CAST(Dqn_f32)(sprite_sheet_row * sprite_sheet->sprite_size.y); - src_rect.size.w = DQN_CAST(Dqn_f32)sprite_sheet->sprite_size.w; - src_rect.size.h = DQN_CAST(Dqn_f32)sprite_sheet->sprite_size.h; + Dqn_usize sprite_sheet_row = sprite_index / sprite.sheet->sprites_per_row; + Dqn_usize sprite_sheet_column = sprite_index % sprite.sheet->sprites_per_row; + src_rect.pos.x = DQN_CAST(Dqn_f32)(sprite_sheet_column * sprite.sheet->sprite_size.w); + src_rect.pos.y = DQN_CAST(Dqn_f32)(sprite_sheet_row * sprite.sheet->sprite_size.y); + src_rect.size.w = DQN_CAST(Dqn_f32)sprite.sheet->sprite_size.w; + src_rect.size.h = DQN_CAST(Dqn_f32)sprite.sheet->sprite_size.h; } break; case TELY_AssetSpriteSheetType_Rects: { - DQN_ASSERT(sprite_index < sprite_sheet->rects.size); - src_rect = sprite_sheet->rects.data[sprite_index]; + DQN_ASSERT(sprite_index < sprite.sheet->rects.size); + src_rect = sprite.sheet->rects.data[sprite_index]; } break; } @@ -1046,10 +1049,13 @@ void FP_Render(FP_Game *game, TELY_Platform *platform, TELY_Renderer *renderer) dest_rect.size = src_rect.size * entity->size_scale; dest_rect.pos = world_pos - (dest_rect.size * .5f); - if (action->flip_on_x) + if (sprite.flip & TELY_AssetFlip_X) dest_rect.size.w *= -1.f; // NOTE: Flip the texture horizontally - TELY_Render_TextureColourV4(renderer, sprite_sheet->tex_handle, src_rect, dest_rect, TELY_COLOUR_WHITE_V4); + if (sprite.flip & TELY_AssetFlip_Y) + dest_rect.size.h *= -1.f; // NOTE: Flip the texture vertically + + TELY_Render_TextureColourV4(renderer, sprite.sheet->tex_handle, src_rect, dest_rect, TELY_COLOUR_WHITE_V4); } // NOTE: Render attack box ================================================================= diff --git a/feely_pona_game.cpp b/feely_pona_game.cpp index 7a21e91..385a114 100644 --- a/feely_pona_game.cpp +++ b/feely_pona_game.cpp @@ -425,32 +425,16 @@ static Dqn_Rect FP_Game_CalcEntityWorldBoundingBox(FP_Game *game, FP_GameEntityH 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, uint32_t 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, FP_GameEntityActionSprite sprite) +static void FP_Game_EntityActionReset(FP_Game *game, FP_GameEntityHandle entity_handle, uint64_t duration_ms, TELY_AssetAnimatedSprite sprite) { - if (!action) + FP_GameEntity *entity = FP_Game_GetEntity(game, entity_handle); + if (!entity) return; - action->sprite = sprite; - 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; + entity->action.sprite = sprite; + entity->action.started_at_clock_ms = game->clock_ms; + entity->action.end_at_clock_ms = DQN_MAX(duration_ms, game->clock_ms + duration_ms); } static Dqn_V2I FP_Game_WorldPosToTilePos(FP_Game *game, Dqn_V2 world_pos) diff --git a/feely_pona_game.h b/feely_pona_game.h index 5e17c98..ac372f6 100644 --- a/feely_pona_game.h +++ b/feely_pona_game.h @@ -60,27 +60,20 @@ struct FP_GameEntitySpawnList FP_GameEntitySpawnList *prev; }; -enum FP_GameEntityActionFlag -{ - FP_GameEntityActionFlag_StateTransition = 1 << 0, - FP_GameEntityActionFlag_Failed = 1 << 1, -}; - struct FP_GameEntityActionSprite { TELY_AssetSpriteSheet *sheet; TELY_AssetSpriteAnimation *anim; }; -Dqn_f32 const FP_GAME_ENTITY_ACTION_INFINITE_TIMER = -1.f; +uint64_t const FP_GAME_ENTITY_ACTION_INFINITE_TIMER = UINT64_MAX; struct FP_GameEntityAction { - bool flip_on_x; - uint32_t state; - uint32_t flags; // Bit flags corresponding with `FP_GameEntityActionFlag` - FP_GameEntityActionSprite sprite; - Dqn_f32 timer_s; - Dqn_f32 end_at_s; + uint32_t state; + uint32_t next_state; + TELY_AssetAnimatedSprite sprite; + uint64_t started_at_clock_ms; + uint64_t end_at_clock_ms; }; enum FP_GameDirection @@ -183,6 +176,8 @@ struct FP_Game FP_GameCamera camera; TELY_RFui rfui; + + uint64_t clock_ms; }; struct FP_GameAStarNode