using System.Collections;
using UnityEngine;
using Animancer;
namespace BaseGames.Enemies.Abilities
{
///
/// 敌人能力抽象基类(架构 07_EnemyModule §8.4)。
/// 责任:生命周期管理、冷却计时、中断分发。子类只实现 。
///
/// 设计要点:
/// - 单一执行实例:同一能力同时只有一个协程在跑。
/// - 协程内 yield WaitForSeconds 复用 (无 GC)。
/// - 受击/死亡时由 调用 。
///
[DisallowMultipleComponent]
public abstract class EnemyAbilityBase : MonoBehaviour, IAttackCandidate
{
[Header("配置 SO")]
[SerializeField] protected EnemyAbilitySO _config;
// 缓存依赖(Awake 填入,热路径无 GetComponent)
protected EnemyBase _enemy;
protected AnimancerComponent _animancer;
protected Transform _transform;
private Coroutine _runner;
private float _cooldownEndTime = -1f;
private bool _isRunning;
// ── 公共状态 ─────────────────────────────────────────────────────
public EnemyAbilitySO Config => _config;
public bool IsRunning => _isRunning;
public AbilityRunState Phase { get; protected set; } = AbilityRunState.Idle;
public float CooldownRemaining => Mathf.Max(0f, _cooldownEndTime - Time.time);
public bool IsOnCooldown => CooldownRemaining > 0f;
/// 能力被外部中断时触发(AI 决策层 / 状态机订阅用)。
public event System.Action Interrupted;
///
/// 统一可用性查询:组件启用 + 冷却完毕 + 未执行中 + 宿主存活。
/// enabled 这一维供阶段门使用——被阶段禁用的能力不得进入选招候选,
/// 否则选招器会选中它然后 StartCoroutine 在禁用组件上必然失败。
///
public virtual bool CanUse => enabled && !_isRunning && !IsOnCooldown
&& _enemy != null && _enemy.IsAlive;
// ── IAttackCandidate(供 EnemyAttackSelector 选招)──────────────────
public bool RequiresLineOfSight => _config != null && _config.requiresLineOfSight;
public bool RequiresGrounded => _config != null && _config.requiresGrounded;
public float Weight => _config != null ? _config.weight : 0f;
public int Priority => _config != null ? _config.priority : 0;
/// 本招圆形攻击范围内是否有玩家(招式自管其射程;LOS 复用敌人级 IsPlayerVisible)。
public virtual bool InAttackRange()
{
if (_config == null || _config.rangeRadius <= 0f
|| _enemy == null || _enemy.PlayerTransform == null) return false;
float sign = _transform.localScale.x < 0f ? -1f : 1f;
Vector2 origin = (Vector2)_transform.position
+ new Vector2(_config.rangeOffset.x * sign, _config.rangeOffset.y);
float r = _config.rangeRadius;
return ((Vector2)_enemy.PlayerTransform.position - origin).sqrMagnitude <= r * r;
}
protected virtual void Awake()
{
_enemy = GetComponentInParent();
_animancer = _enemy != null ? _enemy.Animancer : GetComponentInParent();
_transform = transform;
if (_enemy == null)
Debug.LogError($"[EnemyAbilityBase] {GetType().Name} 找不到 EnemyBase。", this);
if (_animancer == null)
Debug.LogWarning($"[EnemyAbilityBase] {GetType().Name} 找不到 AnimancerComponent,动画能力将无法播放动画。", this);
}
protected virtual void OnDisable()
{
if (_isRunning) Interrupt(InterruptReason.ExternalRequest);
}
// ── 执行 ─────────────────────────────────────────────────────────
///
/// 启动能力。重复调用、冷却中或已运行将返回 false。
/// 若 非空,会先中断同组其他能力(互斥)。
///
public bool Execute()
{
if (!CanUse) return false;
// 互斥组:启动前中断同组正在运行的其他能力
if (_config != null && !string.IsNullOrEmpty(_config.exclusionGroup))
_enemy?.Abilities.InterruptGroup(_config.exclusionGroup, InterruptReason.ExternalRequest);
_runner = StartCoroutine(RunInternal());
return true;
}
///
/// 强制启动能力,忽略冷却检查(连段语义:外部组合技调用子能力时使用)。
/// 若能力正在运行则先中断再重启。
///
public bool ForceExecute()
{
if (_enemy == null || !_enemy.IsAlive) return false;
if (_isRunning) Interrupt(InterruptReason.ExternalRequest);
_runner = StartCoroutine(RunInternal());
return true;
}
private IEnumerator RunInternal()
{
_isRunning = true;
Phase = AbilityRunState.Windup;
try
{
yield return ExecuteCoroutine();
Phase = AbilityRunState.Recovery;
}
finally
{
_isRunning = false;
_runner = null;
_cooldownEndTime = Time.time + (_config != null ? _config.cooldown : 0f);
if (Phase != AbilityRunState.Interrupted) Phase = AbilityRunState.Idle;
OnAbilityEnded();
}
}
/// 子类实现:能力主体。可分多段、含 HitBox 激活/弹幕生成/物理推进等。
protected abstract IEnumerator ExecuteCoroutine();
/// 能力结束钩子(被中断或正常结束都会调用)。
protected virtual void OnAbilityEnded() { }
/// 中断当前执行。冷却仍会按配置计入。
public void Interrupt(InterruptReason reason)
{
if (!_isRunning) return;
if (_config != null)
{
if (reason == InterruptReason.Hurt && !_config.interruptOnHurt) return;
if (reason == InterruptReason.Stagger && !_config.interruptOnStagger) return;
}
if (_runner != null) StopCoroutine(_runner);
_runner = null;
_isRunning = false;
Phase = AbilityRunState.Interrupted;
OnInterrupted(reason);
Interrupted?.Invoke(reason);
OnAbilityEnded();
_cooldownEndTime = Time.time + (_config != null ? _config.cooldown * 0.5f : 0f);
}
protected virtual void OnInterrupted(InterruptReason reason) { }
///
/// 把冷却清回出生态,使能力立刻可用。
/// 供对象池复活()调用:冷却以绝对 Time.time 记时,
/// 不显式清零就会原样活过 despawn/spawn,让新生的敌人带着上一条命的剩余冷却。
/// 注意不能靠 代劳——它有 _isRunning 门(出生时无一在跑),
/// 且其语义是"中断后计半程冷却",是写入冷却而非清除。
///
public void ResetCooldown() => _cooldownEndTime = -1f;
/// 子类辅助:朝向目标(写入输入信号,下一 FixedUpdate 由 EnemyMovement 消费)。
protected void FaceTarget(Transform target)
{
if (target == null || _enemy == null) return;
_enemy.FaceTarget(target.position);
}
/// 把 _config 解析为期望的子类型;类型不符/为空时显式报错(不回退)。
protected T ResolveConfig() where T : EnemyAbilitySO
{
if (_config is T typed) return typed;
Debug.LogError($"[{GetType().Name}] _config 需为 {typeof(T).Name}," +
$"实际 = {(_config != null ? _config.GetType().Name : "null")}。请重建为正确的子类 SO。", this);
return null;
}
#if UNITY_EDITOR
/// 选中时绘制攻击射程范围(编辑器可视化辅助,不影响运行时)。
private void OnDrawGizmosSelected()
{
if (_config == null || _config.category != AbilityCategory.Attack || _config.rangeRadius <= 0f) return;
float sign = transform.localScale.x < 0f ? -1f : 1f;
Vector3 origin = transform.position
+ new Vector3(_config.rangeOffset.x * sign, _config.rangeOffset.y, 0f);
UnityEditor.Handles.color = new Color(1f, 0.4f, 0.2f, 0.9f);
UnityEditor.Handles.DrawWireDisc(origin, Vector3.forward, _config.rangeRadius);
UnityEditor.Handles.Label(origin, $"{_config.abilityId} range");
}
#endif
}
/// WaitForSeconds 池(架构 §10 GC 优化)。能力协程统一通过此获取等待指令。
internal static class EnemyAbilityWaits
{
private const int MaxCacheSize = 64;
private static readonly System.Collections.Generic.Dictionary _cache
= new System.Collections.Generic.Dictionary(32);
public static WaitForSeconds Get(float seconds)
{
if (seconds <= 0f) return null;
if (!_cache.TryGetValue(seconds, out var w))
{
if (_cache.Count < MaxCacheSize)
{
w = new WaitForSeconds(seconds);
_cache[seconds] = w;
}
else
{
return new WaitForSeconds(seconds);
}
}
return w;
}
}
}