234 lines
6.7 KiB
C#
234 lines
6.7 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using SepCore.Definition;
|
|
using UnityEngine;
|
|
|
|
namespace SepCore.Battle
|
|
{
|
|
/// <summary>
|
|
/// 单个属性实例容器。
|
|
/// 维护基础值、修饰器列表,通过脏标记(Dirty Flag)缓存计算结果,并基于 StatConfig 进行边界钳制。
|
|
/// </summary>
|
|
public sealed class CharacterStat
|
|
{
|
|
private readonly List<StatModifier> _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<StatConfig>((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<StatModifier> 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;
|
|
}
|
|
}
|
|
}
|