Phase 1: 复合属性修饰器管线与伤害结算系统

This commit is contained in:
2026-09-24 22:37:22 +08:00
parent ba25ec1926
commit 02f4c67464
35 changed files with 334 additions and 141 deletions
@@ -169,5 +169,89 @@ namespace VampireLike.Tests.Gameplay.EditMode
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}");
}
}
}
@@ -12,7 +12,8 @@ namespace VampireLike.Tests.Gameplay.EditMode
{
var attackerPipeline = new AttributePipeline();
attackerPipeline.SetBaseValue(StatType.MeleeDamage, 20f);
attackerPipeline.SetBaseValue(StatType.DamagePercent, 0.50f); // +50%
attackerPipeline.SetBaseValue(StatType.DamagePercent, 1.0f); // 基础 100% 伤害
attackerPipeline.AddModifier(StatType.DamagePercent, new StatModifier(0.50f, ModifierType.PercentAdd)); // +50% 增伤加成
var context = new DamageContext
{
@@ -28,7 +29,7 @@ namespace VampireLike.Tests.Gameplay.EditMode
};
// Raw = 100 + 20 = 120
// Amplified = 120 * (1 + 0.5) = 180
// Amplified = 120 * (1.0 * (1 + 0.5)) = 180
// Armor = 0 -> Mitigation = 1.0 -> 180
DamageResult result = DamagePipeline.Calculate(in context);
Assert.AreEqual(180, result.Damage);
@@ -246,19 +247,22 @@ namespace VampireLike.Tests.Gameplay.EditMode
[Test]
public void RangeBonus_CalculatesAttackRangeCorrectly()
{
float baseRange = 5.0f; // 5m
float baseRange = 5.0f; // 5m(统一米制)
// 1. 增加 2m (direct units)
// 1. 增加 2.0m 攻击范围 -> 5.0 + 2.0 = 7.0m
float bonus1 = 2.0f;
float eff1 = bonus1 >= 50f ? bonus1 / 1000f : bonus1;
float range1 = UnityEngine.Mathf.Max(0.5f, baseRange + eff1);
float range1 = UnityEngine.Mathf.Max(0.5f, baseRange + bonus1);
Assert.AreEqual(7.0f, range1, 1e-4f);
// 2. 增加 1500 (millimeters)
float bonus2 = 1500f;
float eff2 = bonus2 >= 50f ? bonus2 / 1000f : bonus2;
float range2 = UnityEngine.Mathf.Max(0.5f, baseRange + eff2);
Assert.AreEqual(6.5f, range2, 1e-4f);
// 2. 减少 1.5m 攻击范围 -> 5.0 - 1.5 = 3.5m
float bonus2 = -1.5f;
float range2 = UnityEngine.Mathf.Max(0.5f, baseRange + bonus2);
Assert.AreEqual(3.5f, range2, 1e-4f);
// 3. 极端负向减射程(-10m)触发 0.5m 最小射程保护
float bonus3 = -10.0f;
float range3 = UnityEngine.Mathf.Max(0.5f, baseRange + bonus3);
Assert.AreEqual(0.5f, range3, 1e-4f);
}
[Test]
@@ -312,5 +316,45 @@ namespace VampireLike.Tests.Gameplay.EditMode
UnityEngine.Object.DestroyImmediate(go);
}
[Test]
public void ZeroBaseCrit_NeverProducesCriticalHit()
{
var context = new DamageContext
{
BaseDamage = 100,
BaseCritChance = 0f,
CritMultiplier = 2.0f,
FallbackDefenderArmor = 0f
};
for (int i = 0; i < 100; i++)
{
DamageResult result = DamagePipeline.Calculate(in context);
Assert.IsFalse(result.IsCrit, "Zero BaseCritChance should never produce a critical hit.");
Assert.AreEqual(100, result.Damage);
}
}
[Test]
public void NegativeDamagePercent_DoesNotRevertToOneMultiplier_ScalesDownToMinDamage()
{
var attackerPipeline = new AttributePipeline();
attackerPipeline.SetBaseValue(StatType.DamagePercent, 1.0f);
// 施加 -100% 伤害削弱(伤害倍率降为 0)
attackerPipeline.AddModifier(StatType.DamagePercent, new StatModifier(-1.0f, ModifierType.PercentAdd));
var context = new DamageContext
{
BaseDamage = 100,
AttackerPipeline = attackerPipeline,
FallbackDefenderArmor = 0f
};
DamageResult result = DamagePipeline.Calculate(in context);
// 修复前: damagePercent <= 0 会被重置为 1f,导致造成 100 满额伤害
// 修复后: 伤害倍率为 0,最终受到全局 MinDamage(默认为 1)保底
Assert.AreEqual(1, result.Damage, "Negative or zero damage percent must scale down to min damage (1), never bounce to full damage.");
}
}
}
@@ -6,7 +6,8 @@
"UnityEngine.TestRunner",
"UnityEditor.TestRunner",
"SepCore.Runtime",
"SepCore.Base"
"SepCore.Base",
"SepCore.Presentation"
],
"includePlatforms": [
"Editor"