using System; using System.Collections.Generic; using SepCore.Definition; using UnityEngine; namespace SepCore.Battle { /// /// 单个属性实例容器。 /// 维护基础值、修饰器列表,通过脏标记(Dirty Flag)缓存计算结果,并基于 StatConfig 进行边界钳制。 /// public sealed class CharacterStat { private readonly List _modifiers = new(); private float _baseValue; private bool _isBaseValueExplicitlySet; private bool _isDirty = true; private float _cachedValue; private StatConfig _config; public StatType Type { get; } public StatConfig Config { get { if (_config == null && GameEntry.Luban != null && GameEntry.Luban.IsReady) { _config = GameEntry.Luban.Get((int)Type); if (!_isBaseValueExplicitlySet && _config != null) { _baseValue = _config.DefaultBaseValue; _isDirty = true; } } return _config; } set { _config = value; if (!_isBaseValueExplicitlySet && _config != null) { _baseValue = _config.DefaultBaseValue; } _isDirty = true; } } public float BaseValue { get { if (!_isBaseValueExplicitlySet) { if (Config != null) { _baseValue = Config.DefaultBaseValue; } else if (Type is StatType.DamagePercent or StatType.AttackSpeed) { _baseValue = 1.0f; } } return _baseValue; } set { _isBaseValueExplicitlySet = true; if (Math.Abs(_baseValue - value) > 1e-6f) { _baseValue = value; _isDirty = true; } } } public float Value { get { if (_isDirty) { _cachedValue = CalculateFinalValue(); _isDirty = false; } return _cachedValue; } } public IReadOnlyList Modifiers => _modifiers; public CharacterStat(StatType type, float baseValue = 0f, StatConfig config = null) { Type = type; _config = config; if (Math.Abs(baseValue) > 1e-6f) { _baseValue = baseValue; _isBaseValueExplicitlySet = true; } else if (_config != null) { _baseValue = _config.DefaultBaseValue; _isBaseValueExplicitlySet = true; } else if (type is StatType.DamagePercent or StatType.AttackSpeed) { _baseValue = 1.0f; } _isDirty = true; } public void AddModifier(StatModifier modifier) { if (modifier == null) { throw new ArgumentNullException(nameof(modifier)); } _modifiers.Add(modifier); _isDirty = true; } public bool RemoveModifier(StatModifier modifier) { if (modifier == null) { return false; } if (_modifiers.Remove(modifier)) { _isDirty = true; return true; } return false; } public int RemoveAllModifiersFromSource(object source) { if (source == null) { return 0; } int removed = _modifiers.RemoveAll(m => ReferenceEquals(m.Source, source) || Equals(m.Source, source)); if (removed > 0) { _isDirty = true; } return removed; } public void ClearModifiers() { if (_modifiers.Count > 0) { _modifiers.Clear(); _isDirty = true; } } public void MarkDirty() { _isDirty = true; } private float CalculateFinalValue() { float sumFlat = 0f; float sumPercentAdd = 0f; float prodPercentMult = 1f; for (int i = 0; i < _modifiers.Count; i++) { StatModifier mod = _modifiers[i]; switch (mod.Type) { case ModifierType.Flat: sumFlat += mod.Value; break; case ModifierType.PercentAdd: sumPercentAdd += mod.Value; break; case ModifierType.PercentMult: prodPercentMult *= mod.Value; break; } } float finalValue; if (Type is StatType.CritChance or StatType.Dodge or StatType.LifeSteal) { // 概率/比率型属性(暴击率、闪避率、生命窃取):底数通常为 0~1 的小数概率。 // 无论是 Flat(如道具填 0.27)还是 PercentAdd(如升级奖励填 0.30),本质上均为点数直接加算(+30% 概率)。 // 若沿用标量乘法公式,由于 BaseValue 为 0,会导致 (0 + 0) * (1 + 0.30) = 0,从而百分比奖励完全失效。 finalValue = (BaseValue + sumFlat + sumPercentAdd) * prodPercentMult; } else { // 标量属性与倍率属性(MaxHp、MoveSpeed、DamagePercent、AttackSpeed 等): // (Base + Flat) * (1 + PercentAdd) * PercentMult finalValue = ((BaseValue + sumFlat) * (1f + sumPercentAdd)) * prodPercentMult; } return ClampStat(finalValue); } private float ClampStat(float value) { StatConfig config = Config; if (config != null) { if (config.HasMin && value < config.MinValue) { value = config.MinValue; } if (config.HasMax && value > config.MaxValue) { value = config.MaxValue; } } return value; } } }