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