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);
}
}
}