using System.Collections.Generic; using UnityEngine; using BaseGames.Core; using BaseGames.Core.Events; using BaseGames.Core.Save; namespace BaseGames.Player { /// /// 玩家数值管理组件。负责 HP、灵魂、灵气、弹簧充能、Geo、能力解锁与存档读写。 /// public class PlayerStats : MonoBehaviour, ISaveable, BaseGames.Core.IRestoreOnSave { [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 _onGeoChanged; [SerializeField] private AbilityTypeEventChannelSO _onAbilityUnlocked; [SerializeField] private VoidEventChannelSO _onPlayerDied; [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 CurrentGeo { get; private set; } 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(); // ── 护符属性修改器 ───────────────────────────────────────────────────────── 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; CurrentGeo = _config.InitialGeo; } private void OnEnable() => _onDifficultyChanged?.Subscribe(HandleDifficultyChanged).AddTo(_subs); private void OnDisable() => _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); } } } // ── 护符修改器 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(); if (CurrentHP == 0) _onPlayerDied?.Raise(); } 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 ──────────────────────────────────────────────────────────── public bool UseSpring() { if (CurrentSpringCharges <= 0) return false; CurrentSpringCharges--; _onSpringChargesChanged?.Raise(CurrentSpringCharges); HealHP(_config.SpringHealAmount); 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); } } // ── Geo ─────────────────────────────────────────────────────────────── public void AddGeo(int amount) { if (amount <= 0) return; CurrentGeo += amount; _onGeoChanged?.Raise(CurrentGeo); } public bool SpendGeo(int amount) { if (CurrentGeo < amount) return false; CurrentGeo -= amount; _onGeoChanged?.Raise(CurrentGeo); 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); } public void LockAbility(AbilityType ability) => _unlockedAbilities &= ~ability; // ── ISaveable ───────────────────────────────────────────────────────── public void OnSave(SaveData saveData) { var p = saveData.Player; p.CurrentHP = CurrentHP; p.MaxHP = MaxHP; p.CurrentGeo = CurrentGeo; p.AbilityFlags = (uint)_unlockedAbilities; } public void OnLoad(SaveData saveData) { var p = saveData.Player; MaxHP = p.MaxHP; CurrentHP = Mathf.Clamp(p.CurrentHP, 0, MaxHP); CurrentGeo = p.CurrentGeo; _unlockedAbilities = (AbilityType)p.AbilityFlags; _onHPChanged?.Raise(CurrentHP); _onMaxHPChanged?.Raise(MaxHP); _onGeoChanged?.Raise(CurrentGeo); } } }