Files

283 lines
14 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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);
}
}
}