283 lines
14 KiB
C#
283 lines
14 KiB
C#
using System;
|
||
using NUnit.Framework;
|
||
using SepCore.Battle;
|
||
using SepCore.Definition;
|
||
|
||
namespace VampireLike.Tests.Gameplay.EditMode
|
||
{
|
||
[TestFixture]
|
||
public class AttributePipelineTests
|
||
{
|
||
[Test]
|
||
public void FormulaOrder_CalculatesFlatPercentAddAndPercentMultCorrectly()
|
||
{
|
||
var stat = new CharacterStat(StatType.MeleeDamage, baseValue: 10f);
|
||
|
||
// Formula: ((Base + Sum(Flat)) * (1 + Sum(PercentAdd))) * Prod(PercentMult)
|
||
// Flat: +5, +3 -> Sum = +8 -> Base + Flat = 18
|
||
stat.AddModifier(new StatModifier(5f, ModifierType.Flat));
|
||
stat.AddModifier(new StatModifier(3f, ModifierType.Flat));
|
||
|
||
// PercentAdd: +20% (0.2), +30% (0.3) -> Sum = +50% -> 1 + 0.5 = 1.5
|
||
stat.AddModifier(new StatModifier(0.2f, ModifierType.PercentAdd));
|
||
stat.AddModifier(new StatModifier(0.3f, ModifierType.PercentAdd));
|
||
|
||
// PercentMult: 1.2x, 1.5x -> Prod = 1.8
|
||
stat.AddModifier(new StatModifier(1.2f, ModifierType.PercentMult));
|
||
stat.AddModifier(new StatModifier(1.5f, ModifierType.PercentMult));
|
||
|
||
// Expected: 18 * 1.5 * 1.8 = 48.6
|
||
Assert.AreEqual(48.6f, stat.Value, 1e-4f);
|
||
}
|
||
|
||
[Test]
|
||
public void DirtyFlag_CachesValueUntilModified()
|
||
{
|
||
var stat = new CharacterStat(StatType.MaxHp, baseValue: 100f);
|
||
var mod = new StatModifier(20f, ModifierType.Flat);
|
||
|
||
stat.AddModifier(mod);
|
||
float firstRead = stat.Value;
|
||
Assert.AreEqual(120f, firstRead, 1e-4f);
|
||
|
||
// 读取多次,验证缓存值一致
|
||
float secondRead = stat.Value;
|
||
Assert.AreEqual(120f, secondRead, 1e-4f);
|
||
|
||
// 移除修饰器后脏标记生效,值重新计算
|
||
stat.RemoveModifier(mod);
|
||
Assert.AreEqual(100f, stat.Value, 1e-4f);
|
||
|
||
// 修改 BaseValue 后脏标记生效
|
||
stat.BaseValue = 150f;
|
||
Assert.AreEqual(150f, stat.Value, 1e-4f);
|
||
}
|
||
|
||
[Test]
|
||
public void RemoveAllModifiersFromSource_RemovesOnlyMatchingSource()
|
||
{
|
||
var pipeline = new AttributePipeline();
|
||
var sourceA = new object();
|
||
var sourceB = new object();
|
||
|
||
pipeline.SetBaseValue(StatType.MeleeDamage, 10f);
|
||
pipeline.SetBaseValue(StatType.MoveSpeed, 5f);
|
||
|
||
// sourceA 附加加成到近战伤害与移速
|
||
pipeline.AddModifier(StatType.MeleeDamage, new StatModifier(10f, ModifierType.Flat, sourceA));
|
||
pipeline.AddModifier(StatType.MoveSpeed, new StatModifier(0.2f, ModifierType.PercentAdd, sourceA));
|
||
|
||
// sourceB 附加加成到近战伤害
|
||
pipeline.AddModifier(StatType.MeleeDamage, new StatModifier(5f, ModifierType.Flat, sourceB));
|
||
|
||
Assert.AreEqual(25f, pipeline.GetValue(StatType.MeleeDamage), 1e-4f);
|
||
Assert.AreEqual(6f, pipeline.GetValue(StatType.MoveSpeed), 1e-4f);
|
||
|
||
// 清理 sourceA
|
||
int removedCount = pipeline.RemoveAllModifiersFromSource(sourceA);
|
||
Assert.AreEqual(2, removedCount);
|
||
|
||
// 验证 sourceA 被清除,sourceB 依然生效
|
||
Assert.AreEqual(15f, pipeline.GetValue(StatType.MeleeDamage), 1e-4f);
|
||
Assert.AreEqual(5f, pipeline.GetValue(StatType.MoveSpeed), 1e-4f);
|
||
}
|
||
|
||
[Test]
|
||
public void StatConfig_ClampsMinAndMaxCorrectly()
|
||
{
|
||
// 闪避率硬上限 80% (0.8f) 来自配置表
|
||
var dodgeConfig = new StatConfig(StatType.Dodge, hasMin: true, minValue: 0f, hasMax: true, maxValue: 0.80f);
|
||
var dodgeStat = new CharacterStat(StatType.Dodge, baseValue: 0.50f, dodgeConfig);
|
||
dodgeStat.AddModifier(new StatModifier(0.40f, ModifierType.Flat));
|
||
Assert.AreEqual(0.80f, dodgeStat.Value, 1e-4f);
|
||
|
||
// 移速保底 0.5f 来自配置表
|
||
var speedConfig = new StatConfig(StatType.MoveSpeed, hasMin: true, minValue: 0.5f);
|
||
var speedStat = new CharacterStat(StatType.MoveSpeed, baseValue: 3f, speedConfig);
|
||
speedStat.AddModifier(new StatModifier(-10f, ModifierType.Flat));
|
||
Assert.AreEqual(0.5f, speedStat.Value, 1e-4f);
|
||
|
||
// 暴击率硬上限 90% (0.9f) 来自配置表
|
||
var critConfig = new StatConfig(StatType.CritChance, hasMin: true, minValue: 0f, hasMax: true, maxValue: 0.90f);
|
||
var critStat = new CharacterStat(StatType.CritChance, baseValue: 0.50f, critConfig);
|
||
critStat.AddModifier(new StatModifier(0.60f, ModifierType.Flat));
|
||
Assert.AreEqual(0.90f, critStat.Value, 1e-4f);
|
||
}
|
||
|
||
[Test]
|
||
public void FromLuban_ConvertsFlatAndPercentModifiersCorrectly()
|
||
{
|
||
// 1. 固定值 (Flat): 比如近战伤害 +15
|
||
var flatConfig = new StatModifierConfig(StatType.MeleeDamage, 15f, ModifierType.Flat);
|
||
var flatStatMod = StatModifier.FromLuban(flatConfig);
|
||
Assert.AreEqual(ModifierType.Flat, flatStatMod.Type);
|
||
Assert.AreEqual(15f, flatStatMod.Value, 1e-4f);
|
||
|
||
// 2. 百分比累加 (PercentAdd): 比如伤害 +20% (0.2f)
|
||
var percentConfig = new StatModifierConfig(StatType.DamagePercent, 0.2f, ModifierType.PercentAdd);
|
||
var percentStatMod = StatModifier.FromLuban(percentConfig);
|
||
Assert.AreEqual(ModifierType.PercentAdd, percentStatMod.Type);
|
||
Assert.AreEqual(0.2f, percentStatMod.Value, 1e-4f);
|
||
|
||
// 3. 独立乘区 (PercentMult): 比如伤害 1.5 倍
|
||
var multConfig = new StatModifierConfig(StatType.DamagePercent, 1.5f, ModifierType.PercentMult);
|
||
var multStatMod = StatModifier.FromLuban(multConfig);
|
||
Assert.AreEqual(ModifierType.PercentMult, multStatMod.Type);
|
||
Assert.AreEqual(1.5f, multStatMod.Value, 1e-4f);
|
||
}
|
||
|
||
[Test]
|
||
public void OnStatChanged_FiresOnModifications()
|
||
{
|
||
var pipeline = new AttributePipeline();
|
||
StatType changedStat = StatType.None;
|
||
float changedValue = 0f;
|
||
int eventCount = 0;
|
||
|
||
pipeline.OnStatChanged += (type, val) =>
|
||
{
|
||
changedStat = type;
|
||
changedValue = val;
|
||
eventCount++;
|
||
};
|
||
|
||
// 设置基础值
|
||
pipeline.SetBaseValue(StatType.Armor, 10f);
|
||
Assert.AreEqual(StatType.Armor, changedStat);
|
||
Assert.AreEqual(10f, changedValue, 1e-4f);
|
||
Assert.AreEqual(1, eventCount);
|
||
|
||
// 添加修饰器
|
||
var mod = new StatModifier(5f, ModifierType.Flat);
|
||
pipeline.AddModifier(StatType.Armor, mod);
|
||
Assert.AreEqual(15f, changedValue, 1e-4f);
|
||
Assert.AreEqual(2, eventCount);
|
||
|
||
// 移除修饰器
|
||
pipeline.RemoveModifier(StatType.Armor, mod);
|
||
Assert.AreEqual(10f, changedValue, 1e-4f);
|
||
Assert.AreEqual(3, eventCount);
|
||
}
|
||
|
||
[Test]
|
||
public void DynamicConfig_MarksDirtyAndUpdatesClamp()
|
||
{
|
||
var stat = new CharacterStat(StatType.Dodge, baseValue: 0.90f);
|
||
Assert.AreEqual(0.90f, stat.Value, 1e-4f); // 未注入配置时不钳制
|
||
|
||
// 动态注入配置,硬上限 80%
|
||
stat.Config = new StatConfig(StatType.Dodge, hasMax: true, maxValue: 0.80f);
|
||
Assert.AreEqual(0.80f, stat.Value, 1e-4f); // 自动标记脏并重新钳制
|
||
}
|
||
|
||
[Test]
|
||
public void DamagePercentAndAttackSpeed_DefaultBaseValueIsOne_AndPercentAddScalesCorrectly()
|
||
{
|
||
// 验证 DamagePercent 配置初值为 1 时,+5% 增伤(PercentAdd = 0.05)正确求值为 1.05
|
||
var damageConfig = new StatConfig(StatType.DamagePercent, defaultBaseValue: 1.0f);
|
||
var damageStat = new CharacterStat(StatType.DamagePercent, config: damageConfig);
|
||
Assert.AreEqual(1.0f, damageStat.Value, 1e-4f);
|
||
|
||
// 模拟“肌肉”+5% 伤害
|
||
damageStat.AddModifier(new StatModifier(0.05f, ModifierType.PercentAdd));
|
||
Assert.AreEqual(1.05f, damageStat.Value, 1e-4f);
|
||
|
||
// 模拟额外叠加 +10% 增伤
|
||
damageStat.AddModifier(new StatModifier(0.10f, ModifierType.PercentAdd));
|
||
Assert.AreEqual(1.15f, damageStat.Value, 1e-4f);
|
||
|
||
// 验证 AttackSpeed 配置初值为 1 时,+19% 攻速(PercentAdd = 0.19)正确求值为 1.19
|
||
var speedConfig = new StatConfig(StatType.AttackSpeed, defaultBaseValue: 1.0f);
|
||
var speedStat = new CharacterStat(StatType.AttackSpeed, config: speedConfig);
|
||
Assert.AreEqual(1.0f, speedStat.Value, 1e-4f);
|
||
|
||
// 模拟“疾风手套”+19% 攻速
|
||
speedStat.AddModifier(new StatModifier(0.19f, ModifierType.PercentAdd));
|
||
Assert.AreEqual(1.19f, speedStat.Value, 1e-4f);
|
||
|
||
// 模拟叠加“鼻子”+5% 攻速
|
||
speedStat.AddModifier(new StatModifier(0.05f, ModifierType.PercentAdd));
|
||
Assert.AreEqual(1.24f, speedStat.Value, 1e-4f);
|
||
}
|
||
|
||
[Test]
|
||
public void ItemDescUtility_FormatsDodgeCritPercentAndMoveSpeedCorrectly()
|
||
{
|
||
// 1. 闪避率(Dodge)Flat 修饰器以百分比展示: 0.1 -> +10%, 0.27 -> +27%
|
||
var dodgeMod1 = new StatModifierConfig(StatType.Dodge, 0.1f, ModifierType.Flat);
|
||
string dodgeDesc1 = SepCore.CustomUtility.ItemDescUtility.Describe(dodgeMod1);
|
||
Assert.IsTrue(dodgeDesc1.Contains("+10%"), $"Expected +10% in: {dodgeDesc1}");
|
||
|
||
var dodgeMod2 = new StatModifierConfig(StatType.Dodge, 0.27f, ModifierType.Flat);
|
||
string dodgeDesc2 = SepCore.CustomUtility.ItemDescUtility.Describe(dodgeMod2);
|
||
Assert.IsTrue(dodgeDesc2.Contains("+27%"), $"Expected +27% in: {dodgeDesc2}");
|
||
|
||
// 2. 暴击率(CritChance)Flat 修饰器以百分比展示: 0.27 -> +27%, 0.05 -> +5%
|
||
var critMod1 = new StatModifierConfig(StatType.CritChance, 0.27f, ModifierType.Flat);
|
||
string critDesc1 = SepCore.CustomUtility.ItemDescUtility.Describe(critMod1);
|
||
Assert.IsTrue(critDesc1.Contains("+27%"), $"Expected +27% in: {critDesc1}");
|
||
|
||
var critMod2 = new StatModifierConfig(StatType.CritChance, 0.05f, ModifierType.Flat);
|
||
string critDesc2 = SepCore.CustomUtility.ItemDescUtility.Describe(critMod2);
|
||
Assert.IsTrue(critDesc2.Contains("+5%"), $"Expected +5% in: {critDesc2}");
|
||
|
||
// 3. 移速(MoveSpeed)Flat 修饰器展示原小数: 0.8 -> +0.8
|
||
var speedMod = new StatModifierConfig(StatType.MoveSpeed, 0.8f, ModifierType.Flat);
|
||
string speedDesc = SepCore.CustomUtility.ItemDescUtility.Describe(speedMod);
|
||
Assert.IsTrue(speedDesc.Contains("+0.8"), $"Expected +0.8 in: {speedDesc}");
|
||
Assert.IsFalse(speedDesc.Contains("%"), $"Expected no % in: {speedDesc}");
|
||
|
||
// 4. 攻速(AttackSpeed)PercentAdd 修饰器以百分比展示: 0.19 -> +19%
|
||
var atkSpeedMod = new StatModifierConfig(StatType.AttackSpeed, 0.19f, ModifierType.PercentAdd);
|
||
string atkSpeedDesc = SepCore.CustomUtility.ItemDescUtility.Describe(atkSpeedMod);
|
||
Assert.IsTrue(atkSpeedDesc.Contains("+19%"), $"Expected +19% in: {atkSpeedDesc}");
|
||
|
||
// 5. 伤害(DamagePercent)PercentAdd 修饰器以百分比展示: 0.05 -> +5%
|
||
var dmgMod = new StatModifierConfig(StatType.DamagePercent, 0.05f, ModifierType.PercentAdd);
|
||
string dmgDesc = SepCore.CustomUtility.ItemDescUtility.Describe(dmgMod);
|
||
Assert.IsTrue(dmgDesc.Contains("+5%"), $"Expected +5% in: {dmgDesc}");
|
||
|
||
// 6. 近战伤害(MeleeDamage,非百分比属性)Flat 修饰器展示原数值: 12 -> +12,不带%
|
||
var meleeFlatMod = new StatModifierConfig(StatType.MeleeDamage, 12f, ModifierType.Flat);
|
||
string meleeFlatDesc = SepCore.CustomUtility.ItemDescUtility.Describe(meleeFlatMod);
|
||
Assert.IsTrue(meleeFlatDesc.Contains("+12"), $"Expected +12 in: {meleeFlatDesc}");
|
||
Assert.IsFalse(meleeFlatDesc.Contains("%"), $"Expected no % in: {meleeFlatDesc}");
|
||
|
||
// 7. 移速(MoveSpeed)若为 PercentAdd 修饰器,以百分比展示: 0.15 -> +15%
|
||
var speedPercentMod = new StatModifierConfig(StatType.MoveSpeed, 0.15f, ModifierType.PercentAdd);
|
||
string speedPercentDesc = SepCore.CustomUtility.ItemDescUtility.Describe(speedPercentMod);
|
||
Assert.IsTrue(speedPercentDesc.Contains("+15%"), $"Expected +15% in: {speedPercentDesc}");
|
||
|
||
// 8. PercentMult 乘数修饰器以 x 表示: 1.25 -> x1.25
|
||
var multMod = new StatModifierConfig(StatType.MaxHp, 1.25f, ModifierType.PercentMult);
|
||
string multDesc = SepCore.CustomUtility.ItemDescUtility.Describe(multMod);
|
||
Assert.IsTrue(multDesc.Contains("x1.25"), $"Expected x1.25 in: {multDesc}");
|
||
}
|
||
|
||
[Test]
|
||
public void RateStats_CritChanceAndDodge_ZeroBaseValue_CorrectlyAccumulatesPercentAddModifiers()
|
||
{
|
||
var pipeline = new AttributePipeline();
|
||
|
||
// 1. 验证默认初值为 0
|
||
Assert.AreEqual(0f, pipeline.GetValue(StatType.CritChance), 1e-4f);
|
||
Assert.AreEqual(0f, pipeline.GetValue(StatType.Dodge), 1e-4f);
|
||
|
||
// 2. 模拟选了两个 +30% 暴击率奖励(即升级奖励 1015,ModifierType 为 PercentAdd 0.30)
|
||
pipeline.AddModifier(StatType.CritChance, new StatModifier(0.30f, ModifierType.PercentAdd));
|
||
Assert.AreEqual(0.30f, pipeline.GetValue(StatType.CritChance), 1e-4f);
|
||
|
||
pipeline.AddModifier(StatType.CritChance, new StatModifier(0.30f, ModifierType.PercentAdd));
|
||
Assert.AreEqual(0.60f, pipeline.GetValue(StatType.CritChance), 1e-4f); // 此时暴击率应正确累加到 60%
|
||
|
||
// 3. 模拟叠加 Flat 词条道具(如磨刀石 +27% 暴击率,ModifierType 为 Flat 0.27)
|
||
pipeline.AddModifier(StatType.CritChance, new StatModifier(0.27f, ModifierType.Flat));
|
||
Assert.AreEqual(0.87f, pipeline.GetValue(StatType.CritChance), 1e-4f); // 0.60 + 0.27 = 0.87
|
||
|
||
// 4. 验证闪避率同理:0 基础值下 PercentAdd 0.15 正确累加为 15% 闪避
|
||
pipeline.AddModifier(StatType.Dodge, new StatModifier(0.15f, ModifierType.PercentAdd));
|
||
Assert.AreEqual(0.15f, pipeline.GetValue(StatType.Dodge), 1e-4f);
|
||
}
|
||
}
|
||
}
|