using System.Collections.Generic; using UnityEngine; using BaseGames.Core; using BaseGames.Core.Events; using BaseGames.Core.Save; namespace BaseGames.Player { /// /// 玩家数值管理组件。负责 HP、灵魂、灵气、弹簧充能、LingZhu、能力解锁与存档读写。 /// 实现 供 RewardSO 使用,避免 Quest 程序集直接依赖 Player 程序集。 /// public class PlayerStats : MonoBehaviour, ISaveable, BaseGames.Core.IRestoreOnSave, IRewardTarget, BaseGames.Core.ILingZhuProvider { [Header("配置")] [SerializeField] private PlayerStatsSO _config; [Header("事件频道")] [SerializeField] private IntEventChannelSO _onHPChanged; [SerializeField] private IntEventChannelSO _onMaxHPChanged; [SerializeField] private IntEventChannelSO _onSoulPowerChanged; [SerializeField] private IntEventChannelSO _onSpiritPowerChanged; [SerializeField] private IntEventChannelSO _onSpringChargesChanged; [SerializeField] private IntEventChannelSO _onLingZhuChanged; [SerializeField] private AbilityTypeEventChannelSO _onAbilityUnlocked; [SerializeField] private DifficultyChangedEventChannel _onDifficultyChanged; // ── 运行时数值 ───────────────────────────────────────────────────────── public int CurrentHP { get; private set; } public int MaxHP { get; private set; } public int CurrentSoulPower { get; private set; } public int MaxSoulPower { get; private set; } public int CurrentSpiritPower { get; private set; } public int MaxSpiritPower { get; private set; } public int CurrentSpringCharges { get; private set; } public int MaxSpringCharges { get; private set; } public int SpringKillPoints { get; private set; } public int CurrentLingZhu { get; private set; } private int _lifetimeLingZhu; public bool IsInvincible => _invincibleTimer > 0f; public bool IsAlive => CurrentHP > 0; private float _invincibleTimer; private float _spiritRegenTimer; private AbilityType _unlockedAbilities = AbilityType.None; private bool _isGodMode; private readonly CompositeDisposable _subs = new(); #if UNITY_EDITOR // ── 运行时调试(Inspector 中可见)─────────────────────────────── [Header("\u2500\u2500 \u8fd0\u884c\u65f6\u8c03\u8bd5 \u2500\u2500")] [SerializeField] private string _dbg_HP; [SerializeField] private string _dbg_Soul; [SerializeField] private string _dbg_Spirit; [SerializeField] private string _dbg_Spring; [SerializeField] private bool _dbg_IsInvincible; [SerializeField] private float _dbg_InvincibleTimer; [SerializeField] private bool _dbg_GodMode; [SerializeField] private string _dbg_Abilities; #endif // ── 护符属性修改器 ───────────────────────────────────────────────────────── private readonly Dictionary _flatModifiers = new(); private readonly Dictionary _percentModifiers = new(); private float _animatorSpeedBonus = 0f; private int _soulCostReduction = 0; /// 动画速度倍率(AttackSpeedEffect 调节,初始 1.0)。 public float AnimatorSpeedMultiplier => 1f + _animatorSpeedBonus; /// 法术灵力消耗减免(SoulSpellEffect 叠加)。 public int SoulCostReduction => _soulCostReduction; private void Awake() { Debug.Assert(_config != null, "[PlayerStats] _config 未赋值,请在 Inspector 中指定 PlayerStatsSO。", this); MaxHP = _config.MaxHP; CurrentHP = MaxHP; MaxSoulPower = _config.MaxSoulPower; MaxSpiritPower = _config.MaxSpiritPower; MaxSpringCharges = _config.MaxSpringCharges; CurrentSpringCharges = MaxSpringCharges; CurrentLingZhu = _config.InitialLingZhu; _unlockedAbilities = _config.InitialAbilities; } private void OnEnable() { ServiceLocator.GetOrDefault()?.Register(this); _onDifficultyChanged?.Subscribe(HandleDifficultyChanged).AddTo(_subs); } private void OnDisable() { ServiceLocator.GetOrDefault()?.Unregister(this); _subs.Clear(); } private void HandleDifficultyChanged(DifficultyLevel _) { var scaler = ServiceLocator.GetOrDefault()?.CurrentScaler; if (scaler == null) return; // 按比例缩放当前 HP(架构 19 §5:难度切换时保持 HP 比例) float hpRatio = MaxHP > 0 ? (float)CurrentHP / MaxHP : 1f; MaxHP = Mathf.Max(1, Mathf.RoundToInt(_config.MaxHP * scaler.PlayerMaxHPMultiplier)); CurrentHP = Mathf.Clamp(Mathf.RoundToInt(MaxHP * hpRatio), IsAlive ? 1 : 0, MaxHP); _onMaxHPChanged?.Raise(MaxHP); _onHPChanged?.Raise(CurrentHP); } private void Update() { float dt = Time.deltaTime; if (_invincibleTimer > 0f) _invincibleTimer -= dt; if (_config.SpiritRegenRate > 0) { _spiritRegenTimer += dt; if (_spiritRegenTimer >= 1f) { _spiritRegenTimer -= 1f; AddSpiritPower(_config.SpiritRegenRate); } } #if UNITY_EDITOR // 非字符串字段每帧同步(拷贝值,无分配) _dbg_IsInvincible = IsInvincible; _dbg_InvincibleTimer = _invincibleTimer; _dbg_GodMode = _isGodMode; // 字符串插值限速到 ~10 Hz,避免每帧分配(GC) if (Time.frameCount % 6 == 0) { _dbg_HP = $"{CurrentHP} / {MaxHP}"; _dbg_Soul = $"{CurrentSoulPower} / {MaxSoulPower}"; _dbg_Spirit = $"{CurrentSpiritPower} / {MaxSpiritPower}"; _dbg_Spring = $"{CurrentSpringCharges} / {MaxSpringCharges}"; _dbg_Abilities = _unlockedAbilities == AbilityType.None ? "\u65e0" : _unlockedAbilities.ToString(); } #endif } // ── 护符修改器 API ───────────────────────────────────────────────────── /// 叠加属性修改器(护符效果装备时调用)。 public void AddModifier(StatType stat, float flat, float percent) { _flatModifiers[stat] = _flatModifiers.GetValueOrDefault(stat) + flat; _percentModifiers[stat] = _percentModifiers.GetValueOrDefault(stat) + percent; if (stat == StatType.MaxHP) RecalcMaxHP(); } /// 移除属性修改器(护符效果卸下时调用)。 public void RemoveModifier(StatType stat, float flat, float percent) { _flatModifiers[stat] = _flatModifiers.GetValueOrDefault(stat) - flat; _percentModifiers[stat] = _percentModifiers.GetValueOrDefault(stat) - percent; if (stat == StatType.MaxHP) RecalcMaxHP(); } /// 查询指定属性的固定加成(由 PlayerMovement、战斗等外部系统查询)。 public float GetFlatModifier(StatType stat) => _flatModifiers.GetValueOrDefault(stat); /// 查询指定属性的百分比加成。 public float GetPercentModifier(StatType stat) => _percentModifiers.GetValueOrDefault(stat); /// 加载动画速度加成(AttackSpeedEffect 装备时调用)。 public void AddAnimatorSpeedBonus(float delta) => _animatorSpeedBonus += delta; /// 移除动画速度加成(AttackSpeedEffect 卸下时调用)。 public void RemoveAnimatorSpeedBonus(float delta) => _animatorSpeedBonus -= delta; /// 增加灵力消耗减免(SoulSpellEffect 装备时调用)。 public void AddSoulCostReduction(int amount) => _soulCostReduction += amount; /// 移除灵力消耗减免(SoulSpellEffect 卸下时调用)。 public void RemoveSoulCostReduction(int amount) => _soulCostReduction = Mathf.Max(0, _soulCostReduction - amount); private void RecalcMaxHP() { float flat = _flatModifiers.GetValueOrDefault(StatType.MaxHP); float percent = _percentModifiers.GetValueOrDefault(StatType.MaxHP); int newMax = Mathf.Max(1, Mathf.RoundToInt(_config.MaxHP * (1f + percent) + flat)); SetMaxHP(newMax); } // ── HP ──────────────────────────────────────────────────────────────── /// 受到伤害时触发(已扣血,不包含无敌/死亡情况)。 public event System.Action OnDamaged; /// Debug:开启/关闭无敌模式(不计入无敌帧,永久生效直至关闭)。 public void SetGodMode(bool v) { _isGodMode = v; } public void TakeDamage(int amount) { if (_isGodMode || IsInvincible || !IsAlive || amount <= 0) return; CurrentHP = Mathf.Max(0, CurrentHP - amount); _onHPChanged?.Raise(CurrentHP); OnDamaged?.Invoke(); } /// /// 强制即死,无视无敌帧(危险区域、深渊等环境击杀专用)。 /// GodMode 下仍然豁免。 /// public void Kill() { if (_isGodMode || !IsAlive) return; CurrentHP = 0; _onHPChanged?.Raise(CurrentHP); OnDamaged?.Invoke(); } public void FullHeal() { if (!IsAlive) return; CurrentHP = MaxHP; _onHPChanged?.Raise(CurrentHP); } // ── IRestoreOnSave ──────────────────────────────────────────────────── void BaseGames.Core.IRestoreOnSave.FullRestore() => FullHeal(); void BaseGames.Core.IRestoreOnSave.RestoreSpring() => RestoreSpringCharges(); public void HealHP(int amount) { if (!IsAlive || amount <= 0) return; CurrentHP = Mathf.Min(MaxHP, CurrentHP + amount); _onHPChanged?.Raise(CurrentHP); } public void SetMaxHP(int newMax) { MaxHP = Mathf.Max(1, newMax); CurrentHP = Mathf.Min(CurrentHP, MaxHP); _onMaxHPChanged?.Raise(MaxHP); _onHPChanged?.Raise(CurrentHP); } // ── Soul Power ──────────────────────────────────────────────────────── public void AddSoulPower(int amount) { if (amount <= 0) return; CurrentSoulPower = Mathf.Min(MaxSoulPower, CurrentSoulPower + amount); _onSoulPowerChanged?.Raise(CurrentSoulPower); } /// AddSoul 是 AddSoulPower 的别名(架构 06_CombatModule §8 ParrySystem 使用此名称)。 public void AddSoul(int amount) => AddSoulPower(amount); public bool ConsumeSoulPower(int amount) { if (CurrentSoulPower < amount) return false; CurrentSoulPower -= amount; _onSoulPowerChanged?.Raise(CurrentSoulPower); return true; } // ── Spirit Power ────────────────────────────────────────────────────── public void AddSpiritPower(int amount) { if (amount <= 0) return; CurrentSpiritPower = Mathf.Min(MaxSpiritPower, CurrentSpiritPower + amount); _onSpiritPowerChanged?.Raise(CurrentSpiritPower); } public bool ConsumeSpiritPower(int amount) { if (CurrentSpiritPower < amount) return false; CurrentSpiritPower -= amount; _onSpiritPowerChanged?.Raise(CurrentSpiritPower); return true; } // ── Spring ──────────────────────────────────────────────────────────── /// /// 仅扣除灵泉充能(SpringState 进入前摇时调用)。 /// 回血需前摇结束后调用 。 /// public bool ConsumeSpringCharge() { if (CurrentSpringCharges <= 0) return false; CurrentSpringCharges--; _onSpringChargesChanged?.Raise(CurrentSpringCharges); return true; } /// /// 执行灵泉回血(SpringState 前摇动画结束时调用)。 /// public void ApplySpringHeal() => HealHP(_config.SpringHealAmount); /// 扣除充能并立即回血(兼容旧调用路径)。 public bool UseSpring() { if (!ConsumeSpringCharge()) return false; ApplySpringHeal(); return true; } public void RestoreSpringCharges(int amount = -1) { if (amount < 0) amount = MaxSpringCharges; CurrentSpringCharges = Mathf.Min(MaxSpringCharges, CurrentSpringCharges + amount); _onSpringChargesChanged?.Raise(CurrentSpringCharges); } public void AddKillPoints(int points = 1) { SpringKillPoints += points; if (SpringKillPoints >= _config.SpringKillThreshold) { SpringKillPoints = 0; RestoreSpringCharges(1); } } // ── LingZhu ───────────────────────────────────────────────────────────────────── public void AddLingZhu(int amount) { if (amount <= 0) return; CurrentLingZhu += amount; _lifetimeLingZhu += amount; _onLingZhuChanged?.Raise(CurrentLingZhu); } public bool SpendLingZhu(int amount) { if (CurrentLingZhu < amount) return false; CurrentLingZhu -= amount; _onLingZhuChanged?.Raise(CurrentLingZhu); return true; } // ── Invincibility ───────────────────────────────────────────────────── public void BeginInvincibility(float duration = -1f) { float d = duration >= 0f ? duration : _config.InvincibilityDuration; _invincibleTimer = Mathf.Max(_invincibleTimer, d); } // ── Abilities ───────────────────────────────────────────────────────── public bool HasAbility(AbilityType ability) => (_unlockedAbilities & ability) == ability; public void UnlockAbility(AbilityType ability) { if (HasAbility(ability)) return; _unlockedAbilities |= ability; _onAbilityUnlocked?.Raise(ability); } /// IRewardTarget 实现:以 uint 位掩码解锁能力(避免跨程序集枚举引用)。 void IRewardTarget.UnlockAbilityFlag(uint abilityFlag) => UnlockAbility((AbilityType)abilityFlag); public void LockAbility(AbilityType ability) => _unlockedAbilities &= ~ability; // ── ISaveable ───────────────────────────────────────────────────────── public void OnSave(SaveData saveData) { var p = saveData.Player; p.CurrentHP = CurrentHP; p.MaxHP = MaxHP; p.CurrentLingZhu = CurrentLingZhu; p.LifetimeLingZhu = _lifetimeLingZhu; p.AbilityFlags = (uint)_unlockedAbilities; } public void OnLoad(SaveData saveData) { var p = saveData.Player; MaxHP = p.MaxHP; CurrentHP = Mathf.Clamp(p.CurrentHP, 0, MaxHP); CurrentLingZhu = p.CurrentLingZhu; _lifetimeLingZhu = p.LifetimeLingZhu; _unlockedAbilities = (AbilityType)p.AbilityFlags; _onHPChanged?.Raise(CurrentHP); _onMaxHPChanged?.Raise(MaxHP); _onLingZhuChanged?.Raise(CurrentLingZhu); } } }