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}"); } } }