Files
vampire-like/Assets/Tests/Gameplay/EditMode/AttributePipelineTests.cs
T
2026-09-18 21:43:22 +08:00

174 lines
7.4 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); // 自动标记脏并重新钳制
}
}
}