using UnityEngine;
using BaseGames.Combat;
namespace BaseGames.Player.States
{
///
/// 地面攻击状态(任意段连击)。
/// 攻速倍率(Stats.AnimatorSpeedMultiplier)缩放 clip 播放速度及 recoveryTime/comboTimeout。
/// 状态机流程:
/// 播放动画 → [comboInputOpen] 接受输入 → [cancelWindowOpen] 允许跳跃/冲刺 →
/// 动画结束 → 硬直(recoveryTime) → 连击等待(comboTimeout) → 无输入则回 Idle
///
public class AttackState : PlayerStateBase
{
private int _comboIndex;
private bool _comboInputPending; // 连击窗口内已收到攻击输入
private bool _comboWindowOpen; // 当前是否接受连击输入
// 动画结束后的两阶段计时
private bool _waitingAfterAnim; // 是否在动画结束后等待阶段
private float _recoveryEndTime; // 硬直结束时刻
private float _comboTimeoutEnd; // 连击等待结束时刻
public AttackState(PlayerController owner) : base(owner) { }
public override void OnStateEnter()
{
_comboIndex = 0;
_comboInputPending = false;
_comboWindowOpen = false;
_waitingAfterAnim = false;
Input.AttackEvent += OnAttackInput;
PlayAttackClip();
}
public override void OnStateExit()
{
Input.AttackEvent -= OnAttackInput;
Owner.Combat?.DisableAllWeaponHitBoxes();
Move?.SetCancelWindowOpen(false);
}
public override void OnStateUpdate()
{
if (!_waitingAfterAnim)
{
// ── 动画播放中:只处理取消窗口(跳跃/冲刺打断)──────────────
if (!Move.CancelWindowOpen) return;
TryConsumeCancelInput();
return;
}
// ── 动画结束后等待阶段 ─────────────────────────────────────────
float now = Time.time;
// 硬直结束后开放取消窗口
if (!Move.CancelWindowOpen && now >= _recoveryEndTime)
Move.SetCancelWindowOpen(true);
// 有缓存的连击输入 → 立即推进
if (_comboInputPending)
{
AdvanceCombo();
return;
}
// 连击超时 → 返回 Idle
if (now >= _comboTimeoutEnd)
{
Owner.TransitionTo(Owner.GetState());
return;
}
// 恢复期结束后仍可跳跃/冲刺取消
if (Move.CancelWindowOpen)
TryConsumeCancelInput();
}
public override void OnStateFixedUpdate()
{
// 攻击动画播放期间无水平输入,仍需每帧调用 Move(0) 叠加 _platformVelocity.x,
// 使玩家在移动平台上攻击时仍随平台同步移动。
if (Move.IsGrounded)
Move.Move(0f);
}
// ── 内部 ──────────────────────────────────────────────────────────────
private void PlayAttackClip()
{
_waitingAfterAnim = false;
_comboWindowOpen = false;
Move.SetCancelWindowOpen(false);
var weapon = Owner.Weapon?.ActiveWeapon;
if (weapon == null)
{
UnityEngine.Debug.LogWarning("[AttackState] 未找到 ActiveWeapon,请检查 WeaponManager 配置。");
Owner.TransitionTo(Owner.GetState());
return;
}
var step = weapon.GetGroundStep(_comboIndex);
if (step.clip == null || step.clip.Clip == null)
{
UnityEngine.Debug.LogWarning($"[AttackState] 连击段 {_comboIndex} 动画未配置,请检查 {weapon.weaponId}。");
Owner.TransitionTo(Owner.GetState());
return;
}
float spd = Stats?.AnimatorSpeedMultiplier ?? 1f;
var animState = Anim.Play(step.clip);
animState.Speed *= spd; // 在 ClipTransition 自身速度基础上叠加攻速
var events = animState.Events(this);
events.OnEnd = OnClipEnd;
// HitBox 时间窗口(capture step by value for closure safety)
var capturedStep = step;
events.Add(capturedStep.hitBoxEnter, () =>
Owner.Combat?.EnableWeaponHitBox(AttackDirection.Ground,
capturedStep.hitBoxId, capturedStep.damageSource));
events.Add(capturedStep.hitBoxExit, () => Owner.Combat?.DisableAllWeaponHitBoxes());
// 连击输入窗口
if (capturedStep.comboInputOpen > 0f)
events.Add(capturedStep.comboInputOpen, () => _comboWindowOpen = true);
else
_comboWindowOpen = true; // 0 = 立即开放
if (capturedStep.comboInputClose > 0f)
events.Add(capturedStep.comboInputClose, () => _comboWindowOpen = false);
// 取消窗口(跳跃/冲刺)
if (capturedStep.cancelWindowOpen > 0f)
events.Add(capturedStep.cancelWindowOpen, () => Move.SetCancelWindowOpen(true));
}
private void OnClipEnd()
{
Owner.Combat?.DisableAllWeaponHitBoxes();
_comboWindowOpen = false;
Move.SetCancelWindowOpen(false);
// 如果已有缓存输入,直接推进(零延迟连击)
if (_comboInputPending)
{
AdvanceCombo();
return;
}
// 进入动画后等待阶段
var step = Owner.Weapon?.ActiveWeapon?.GetGroundStep(_comboIndex) ?? default;
float spd = Stats?.AnimatorSpeedMultiplier ?? 1f;
float now = Time.time;
_waitingAfterAnim = true;
_recoveryEndTime = now + step.recoveryTime / spd;
_comboTimeoutEnd = _recoveryEndTime + step.comboTimeout / spd;
}
private void AdvanceCombo()
{
int maxCombo = Owner.Weapon?.ActiveWeapon?.GroundComboCount ?? 1;
if (_comboIndex < maxCombo - 1)
{
_comboIndex++;
_comboInputPending = false;
PlayAttackClip();
}
else
{
// 已是最后一段,忽略多余输入,等待超时
_comboInputPending = false;
}
}
private void TryConsumeCancelInput()
{
if (Buffer.ConsumeJump())
{
Owner.TransitionTo(Owner.GetState());
return;
}
var ds = Owner.GetState();
if (ds != null && ds.CanDash
&& Stats != null && Stats.HasAbility(AbilityType.Dash)
&& Buffer.ConsumeDash())
{
Owner.TransitionTo(ds);
}
}
private void OnAttackInput()
{
if (_comboWindowOpen || _waitingAfterAnim)
_comboInputPending = true;
}
}
}