S7-03 + S7-04 + S7-05
This commit is contained in:
@@ -24,7 +24,7 @@ namespace GeometryTD.Tests.EditMode
|
||||
}
|
||||
};
|
||||
|
||||
HitContext hitContext = TagEffectResolver.ResolveBeforeHit(attackPayload, 100, 100, false, 0.05f);
|
||||
HitContext hitContext = TagEffectResolver.ResolveBeforeHit(CreateHitContext(attackPayload, 100, 100, 1f, null, 0.05f));
|
||||
|
||||
Assert.That(hitContext.IsCriticalHit, Is.True);
|
||||
Assert.That(hitContext.FinalDamage, Is.EqualTo(150));
|
||||
@@ -43,8 +43,8 @@ namespace GeometryTD.Tests.EditMode
|
||||
}
|
||||
};
|
||||
|
||||
HitContext lowHealthHit = TagEffectResolver.ResolveBeforeHit(attackPayload, 30, 100, false);
|
||||
HitContext highHealthHit = TagEffectResolver.ResolveBeforeHit(attackPayload, 31, 100, false);
|
||||
HitContext lowHealthHit = TagEffectResolver.ResolveBeforeHit(CreateHitContext(attackPayload, 30, 100));
|
||||
HitContext highHealthHit = TagEffectResolver.ResolveBeforeHit(CreateHitContext(attackPayload, 31, 100));
|
||||
|
||||
Assert.That(lowHealthHit.FinalDamage, Is.EqualTo(150));
|
||||
Assert.That(highHealthHit.FinalDamage, Is.EqualTo(100));
|
||||
@@ -63,8 +63,8 @@ namespace GeometryTD.Tests.EditMode
|
||||
}
|
||||
};
|
||||
|
||||
HitContext slowedHit = TagEffectResolver.ResolveBeforeHit(attackPayload, 100, 100, true);
|
||||
HitContext normalHit = TagEffectResolver.ResolveBeforeHit(attackPayload, 100, 100, false);
|
||||
HitContext slowedHit = TagEffectResolver.ResolveBeforeHit(CreateHitContext(attackPayload, 100, 100, 0.6f, new[] { TagType.Ice }));
|
||||
HitContext normalHit = TagEffectResolver.ResolveBeforeHit(CreateHitContext(attackPayload, 100, 100));
|
||||
|
||||
Assert.That(slowedHit.FinalDamage, Is.EqualTo(150));
|
||||
Assert.That(normalHit.FinalDamage, Is.EqualTo(100));
|
||||
@@ -85,7 +85,7 @@ namespace GeometryTD.Tests.EditMode
|
||||
}
|
||||
};
|
||||
|
||||
HitContext hitContext = TagEffectResolver.ResolveBeforeHit(attackPayload, 30, 100, true, 0.05f);
|
||||
HitContext hitContext = TagEffectResolver.ResolveBeforeHit(CreateHitContext(attackPayload, 30, 100, 0.6f, new[] { TagType.Ice }, 0.05f));
|
||||
|
||||
Assert.That(hitContext.IsCriticalHit, Is.True);
|
||||
Assert.That(hitContext.FinalDamage, Is.EqualTo(281));
|
||||
@@ -125,6 +125,63 @@ namespace GeometryTD.Tests.EditMode
|
||||
|
||||
Assert.That(stack, Is.EqualTo(2));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ResolveBeforeHit_Uses_Context_TargetHealth_And_CritRoll()
|
||||
{
|
||||
AttackPayload attackPayload = new AttackPayload
|
||||
{
|
||||
BaseDamage = 100,
|
||||
TagRuntimes = new[]
|
||||
{
|
||||
new TagRuntimeData { TagType = TagType.Crit, TotalStack = 1 },
|
||||
new TagRuntimeData { TagType = TagType.Execution, TotalStack = 1 }
|
||||
}
|
||||
};
|
||||
|
||||
HitContext hitContext = TagEffectResolver.ResolveBeforeHit(CreateHitContext(attackPayload, 30, 100, 1f, null, 0.05f));
|
||||
|
||||
Assert.That(hitContext.IsCriticalHit, Is.True);
|
||||
Assert.That(hitContext.FinalDamage, Is.EqualTo(225));
|
||||
Assert.That(hitContext.IsKilled, Is.True);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ResolveBeforeHit_Shatter_Uses_Context_SlowSnapshot()
|
||||
{
|
||||
AttackPayload attackPayload = new AttackPayload
|
||||
{
|
||||
BaseDamage = 100,
|
||||
TagRuntimes = new[]
|
||||
{
|
||||
new TagRuntimeData { TagType = TagType.Shatter, TotalStack = 1 }
|
||||
}
|
||||
};
|
||||
|
||||
HitContext hitContext = TagEffectResolver.ResolveBeforeHit(CreateHitContext(attackPayload, 100, 100, 0.8f, new[] { TagType.Ice }));
|
||||
|
||||
Assert.That(hitContext.FinalDamage, Is.EqualTo(125));
|
||||
}
|
||||
|
||||
private static HitContext CreateHitContext(
|
||||
AttackPayload attackPayload,
|
||||
int targetCurrentHealthBeforeHit,
|
||||
int targetMaxHealth,
|
||||
float targetMoveSpeedMultiplierBeforeHit = 1f,
|
||||
TagType[] targetStatusTagsBeforeHit = null,
|
||||
float? critRoll = null)
|
||||
{
|
||||
return new HitContext
|
||||
{
|
||||
AttackPayload = attackPayload,
|
||||
TargetCurrentHealthBeforeHit = targetCurrentHealthBeforeHit,
|
||||
TargetCurrentHealthAfterHit = targetCurrentHealthBeforeHit,
|
||||
TargetMaxHealth = targetMaxHealth,
|
||||
TargetMoveSpeedMultiplierBeforeHit = targetMoveSpeedMultiplierBeforeHit,
|
||||
TargetStatusTagsBeforeHit = targetStatusTagsBeforeHit ?? System.Array.Empty<TagType>(),
|
||||
CritRoll = critRoll
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class EnemyTagStatusRuntimeTests
|
||||
@@ -143,7 +200,7 @@ namespace GeometryTD.Tests.EditMode
|
||||
};
|
||||
int totalDamage = 0;
|
||||
|
||||
TagEffectResolver.ApplyAfterHit(attackPayload, runtime);
|
||||
TagEffectResolver.ApplyAfterHit(CreateAfterHitContext(attackPayload, runtime));
|
||||
Assert.That(runtime.HasStatus(TagType.Fire), Is.True);
|
||||
|
||||
runtime.Tick(1f, damage => totalDamage += damage);
|
||||
@@ -154,6 +211,32 @@ namespace GeometryTD.Tests.EditMode
|
||||
Assert.That(totalDamage, Is.EqualTo(120));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ApplyAfterHit_Fire_MaxEffectiveStack_Clamps_BaseFireStack_Only()
|
||||
{
|
||||
DRTagConfig fireRow = new DRTagConfig();
|
||||
Assert.That(fireRow.ParseDataRow("\t1\t元素\tFire\tOnAfterHit\t火焰测试描述\t{\"BurnDurationSeconds\":3,\"BurnDamagePerSecondPerStack\":20,\"MaxEffectiveStack\":2}", null), Is.True);
|
||||
TagDefinitionRegistry.LoadFromRows(new[] { fireRow });
|
||||
|
||||
EnemyTagStatusRuntime runtime = new EnemyTagStatusRuntime();
|
||||
AttackPayload attackPayload = new AttackPayload
|
||||
{
|
||||
BaseDamage = 100,
|
||||
TagRuntimes = new[]
|
||||
{
|
||||
new TagRuntimeData { TagType = TagType.Fire, TotalStack = 4 }
|
||||
}
|
||||
};
|
||||
int totalDamage = 0;
|
||||
|
||||
TagEffectResolver.ApplyAfterHit(CreateAfterHitContext(attackPayload, runtime));
|
||||
runtime.Tick(3f, damage => totalDamage += damage);
|
||||
|
||||
Assert.That(totalDamage, Is.EqualTo(120));
|
||||
|
||||
TagDefinitionRegistry.ResetToDefaults();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ApplyAfterHit_Fire_UsesElapsedDeltaTimeInsteadOfIndependentInterval()
|
||||
{
|
||||
@@ -168,7 +251,7 @@ namespace GeometryTD.Tests.EditMode
|
||||
};
|
||||
int totalDamage = 0;
|
||||
|
||||
TagEffectResolver.ApplyAfterHit(attackPayload, runtime);
|
||||
TagEffectResolver.ApplyAfterHit(CreateAfterHitContext(attackPayload, runtime));
|
||||
|
||||
runtime.Tick(0.5f, damage => totalDamage += damage);
|
||||
runtime.Tick(0.5f, damage => totalDamage += damage);
|
||||
@@ -191,7 +274,7 @@ namespace GeometryTD.Tests.EditMode
|
||||
}
|
||||
};
|
||||
|
||||
TagEffectResolver.ApplyAfterHit(attackPayload, runtime);
|
||||
TagEffectResolver.ApplyAfterHit(CreateAfterHitContext(attackPayload, runtime));
|
||||
|
||||
Assert.That(runtime.HasStatus(TagType.Ice), Is.True);
|
||||
Assert.That(runtime.GetMoveSpeedMultiplier(), Is.EqualTo(0.6f).Within(0.001f));
|
||||
@@ -217,7 +300,7 @@ namespace GeometryTD.Tests.EditMode
|
||||
};
|
||||
int totalDamage = 0;
|
||||
|
||||
TagEffectResolver.ApplyAfterHit(attackPayload, runtime);
|
||||
TagEffectResolver.ApplyAfterHit(CreateAfterHitContext(attackPayload, runtime));
|
||||
Assert.That(runtime.HasStatus(TagType.Fire), Is.True);
|
||||
Assert.That(runtime.HasStatus(TagType.Inferno), Is.False);
|
||||
|
||||
@@ -231,6 +314,35 @@ namespace GeometryTD.Tests.EditMode
|
||||
Assert.That(runtime.HasStatus(TagType.Fire), Is.False);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ApplyAfterHit_FireWithInferno_StillAppliesInfernoBonusAfterFireCap()
|
||||
{
|
||||
DRTagConfig fireRow = new DRTagConfig();
|
||||
DRTagConfig infernoRow = new DRTagConfig();
|
||||
Assert.That(fireRow.ParseDataRow("\t1\t元素\tFire\tOnAfterHit\t火焰测试描述\t{\"BurnDurationSeconds\":3,\"BurnDamagePerSecondPerStack\":20,\"MaxEffectiveStack\":2}", null), Is.True);
|
||||
Assert.That(infernoRow.ParseDataRow("\t4\t元素\tInferno\tOnAfterHit\t强化燃烧伤害或持续时间\t{\"BonusBurnDurationSeconds\":0.5,\"BonusBurnDamagePerSecondPerStack\":10}", null), Is.True);
|
||||
TagDefinitionRegistry.LoadFromRows(new[] { fireRow, infernoRow });
|
||||
|
||||
EnemyTagStatusRuntime runtime = new EnemyTagStatusRuntime();
|
||||
AttackPayload attackPayload = new AttackPayload
|
||||
{
|
||||
BaseDamage = 100,
|
||||
TagRuntimes = new[]
|
||||
{
|
||||
new TagRuntimeData { TagType = TagType.Fire, TotalStack = 4 },
|
||||
new TagRuntimeData { TagType = TagType.Inferno, TotalStack = 1 }
|
||||
}
|
||||
};
|
||||
int totalDamage = 0;
|
||||
|
||||
TagEffectResolver.ApplyAfterHit(CreateAfterHitContext(attackPayload, runtime));
|
||||
runtime.Tick(3.5f, damage => totalDamage += damage);
|
||||
|
||||
Assert.That(totalDamage, Is.EqualTo(175));
|
||||
|
||||
TagDefinitionRegistry.ResetToDefaults();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ApplyAfterHit_InfernoWithoutFire_DoesNotCreateStatus()
|
||||
{
|
||||
@@ -244,12 +356,41 @@ namespace GeometryTD.Tests.EditMode
|
||||
}
|
||||
};
|
||||
|
||||
TagEffectResolver.ApplyAfterHit(attackPayload, runtime);
|
||||
TagEffectResolver.ApplyAfterHit(CreateAfterHitContext(attackPayload, runtime));
|
||||
|
||||
Assert.That(runtime.HasStatus(TagType.Fire), Is.False);
|
||||
Assert.That(runtime.HasStatus(TagType.Inferno), Is.False);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ApplyAfterHit_StatusModifier_Uses_Independent_OnAfterHit_Route()
|
||||
{
|
||||
DRTagConfig infernoRow = new DRTagConfig();
|
||||
Assert.That(infernoRow.ParseDataRow("\t4\t元素\tInferno\tOnHit\t强化燃烧伤害或持续时间\t{\"BonusBurnDurationSeconds\":1,\"BonusBurnDamagePerSecondPerStack\":20}", null), Is.True);
|
||||
TagDefinitionRegistry.LoadFromRows(new[] { infernoRow });
|
||||
|
||||
EnemyTagStatusRuntime runtime = new EnemyTagStatusRuntime();
|
||||
HitContext hitContext = CreateAfterHitContext(new AttackPayload
|
||||
{
|
||||
BaseDamage = 100,
|
||||
TagRuntimes = new[]
|
||||
{
|
||||
new TagRuntimeData { TagType = TagType.Fire, TotalStack = 1 },
|
||||
new TagRuntimeData { TagType = TagType.Inferno, TotalStack = 1 }
|
||||
}
|
||||
}, runtime);
|
||||
int totalDamage = 0;
|
||||
|
||||
TagEffectResolver.ApplyAfterHit(hitContext);
|
||||
runtime.Tick(3f, damage => totalDamage += damage);
|
||||
|
||||
Assert.That(totalDamage, Is.EqualTo(60));
|
||||
Assert.That(hitContext.StatusModifierContext.BonusBurnDamagePerSecond, Is.EqualTo(0f).Within(0.001f));
|
||||
Assert.That(hitContext.StatusModifierContext.BonusBurnDurationSeconds, Is.EqualTo(0f).Within(0.001f));
|
||||
|
||||
TagDefinitionRegistry.ResetToDefaults();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ApplyAfterHit_IceWithAbsoluteZero_IncreasesSlowStrengthAndDuration()
|
||||
{
|
||||
@@ -264,7 +405,7 @@ namespace GeometryTD.Tests.EditMode
|
||||
}
|
||||
};
|
||||
|
||||
TagEffectResolver.ApplyAfterHit(attackPayload, runtime);
|
||||
TagEffectResolver.ApplyAfterHit(CreateAfterHitContext(attackPayload, runtime));
|
||||
|
||||
Assert.That(runtime.HasStatus(TagType.Ice), Is.True);
|
||||
Assert.That(runtime.HasStatus(TagType.AbsoluteZero), Is.False);
|
||||
@@ -291,7 +432,7 @@ namespace GeometryTD.Tests.EditMode
|
||||
}
|
||||
};
|
||||
|
||||
TagEffectResolver.ApplyAfterHit(attackPayload, runtime);
|
||||
TagEffectResolver.ApplyAfterHit(CreateAfterHitContext(attackPayload, runtime));
|
||||
|
||||
Assert.That(runtime.HasStatus(TagType.Ice), Is.False);
|
||||
Assert.That(runtime.HasStatus(TagType.AbsoluteZero), Is.False);
|
||||
@@ -323,12 +464,12 @@ namespace GeometryTD.Tests.EditMode
|
||||
|
||||
EnemyTagStatusRuntime runtime = new EnemyTagStatusRuntime();
|
||||
int totalDamage = 0;
|
||||
TagEffectResolver.ApplyAfterHit(new AttackPayload
|
||||
TagEffectResolver.ApplyAfterHit(CreateAfterHitContext(new AttackPayload
|
||||
{
|
||||
BaseDamage = tower.Stats.AttackDamage[0],
|
||||
AttackPropertyType = tower.Stats.AttackPropertyType,
|
||||
TagRuntimes = tower.Stats.TagRuntimes
|
||||
}, runtime);
|
||||
}, runtime));
|
||||
|
||||
runtime.Tick(1f, damage => totalDamage += damage);
|
||||
runtime.Tick(1f, damage => totalDamage += damage);
|
||||
@@ -337,6 +478,35 @@ namespace GeometryTD.Tests.EditMode
|
||||
Assert.That(runtime.HasStatus(TagType.Fire), Is.False);
|
||||
Assert.That(totalDamage, Is.EqualTo(180));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ApplyAfterHit_Uses_TargetStatusRuntime_From_HitContext()
|
||||
{
|
||||
EnemyTagStatusRuntime runtime = new EnemyTagStatusRuntime();
|
||||
HitContext hitContext = CreateAfterHitContext(new AttackPayload
|
||||
{
|
||||
BaseDamage = 100,
|
||||
TagRuntimes = new[]
|
||||
{
|
||||
new TagRuntimeData { TagType = TagType.Ice, TotalStack = 1 }
|
||||
}
|
||||
}, runtime);
|
||||
|
||||
TagEffectResolver.ApplyAfterHit(hitContext);
|
||||
|
||||
Assert.That(runtime.HasStatus(TagType.Ice), Is.True);
|
||||
Assert.That(hitContext.TargetStatusRuntime, Is.SameAs(runtime));
|
||||
}
|
||||
|
||||
private static HitContext CreateAfterHitContext(AttackPayload attackPayload, EnemyTagStatusRuntime runtime)
|
||||
{
|
||||
return new HitContext
|
||||
{
|
||||
AttackPayload = attackPayload,
|
||||
TargetStatusRuntime = runtime,
|
||||
TargetStatusTagsBeforeHit = System.Array.Empty<TagType>()
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class EnemyStatusTagRegistryTests
|
||||
@@ -372,8 +542,8 @@ namespace GeometryTD.Tests.EditMode
|
||||
AssertDefinition(TagType.Fire, TagCategory.Status, TagTriggerPhase.OnAfterHit);
|
||||
AssertDefinition(TagType.Inferno, TagCategory.StatusModifier, TagTriggerPhase.OnAfterHit);
|
||||
AssertDefinition(TagType.Crit, TagCategory.NumericModifier, TagTriggerPhase.OnBeforeHit);
|
||||
AssertDefinition(TagType.Pierce, TagCategory.AttackShape, TagTriggerPhase.OnHit);
|
||||
AssertDefinition(TagType.BurnSpread, TagCategory.AttackShape, TagTriggerPhase.OnKill);
|
||||
AssertDefinition(TagType.Pierce, TagCategory.AttackShape, TagTriggerPhase.None);
|
||||
AssertDefinition(TagType.BurnSpread, TagCategory.AttackShape, TagTriggerPhase.None);
|
||||
AssertDefinition(TagType.AbsoluteZero, TagCategory.StatusModifier, TagTriggerPhase.OnAfterHit);
|
||||
}
|
||||
|
||||
@@ -519,10 +689,21 @@ namespace GeometryTD.Tests.EditMode
|
||||
};
|
||||
|
||||
Assert.DoesNotThrow(() => TagEffectResolver.ApplyOnHit(hitContext));
|
||||
Assert.DoesNotThrow(() => TagEffectResolver.ApplyAfterHit(hitContext.AttackPayload, new EnemyTagStatusRuntime()));
|
||||
hitContext.TargetStatusRuntime = new EnemyTagStatusRuntime();
|
||||
Assert.DoesNotThrow(() => TagEffectResolver.ApplyAfterHit(hitContext));
|
||||
Assert.DoesNotThrow(() => TagEffectResolver.ApplyOnKill(hitContext));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Placeholder_AttackShape_Configs_Do_Not_Expose_Formal_Runtime_Params()
|
||||
{
|
||||
Assert.That(typeof(BurnSpreadTagConfig).GetProperty("SpreadRadius"), Is.Null);
|
||||
Assert.That(typeof(IgniteBurstTagConfig).GetProperty("BurstRadius"), Is.Null);
|
||||
Assert.That(typeof(FreezeMaskTagConfig).GetProperty("FreezeBuildUpPerStack"), Is.Null);
|
||||
Assert.That(typeof(PierceTagConfig).GetProperty("ExtraPierceCount"), Is.Null);
|
||||
Assert.That(typeof(OverpenetrateTagConfig).GetProperty("RemainingDamageRate"), Is.Null);
|
||||
}
|
||||
|
||||
private static void AssertDefinition(TagType tagType, TagCategory expectedCategory, TagTriggerPhase expectedPhase)
|
||||
{
|
||||
Assert.That(TagDefinitionRegistry.TryGetDefinition(tagType, out TagDefinition definition), Is.True);
|
||||
@@ -577,6 +758,9 @@ namespace GeometryTD.Tests.EditMode
|
||||
{
|
||||
BaseDamage = 80,
|
||||
AttackPropertyType = AttackPropertyType.Fire,
|
||||
SourceEntityId = 101,
|
||||
ProjectileEntityId = 202,
|
||||
OriginPosition = new Vector3(1f, 2f, 0f),
|
||||
TagRuntimes = new[]
|
||||
{
|
||||
new TagRuntimeData { TagType = TagType.Fire, TotalStack = 1 }
|
||||
@@ -590,6 +774,9 @@ namespace GeometryTD.Tests.EditMode
|
||||
Assert.That(receiver.LastPayload, Is.Not.Null);
|
||||
Assert.That(receiver.LastPayload.BaseDamage, Is.EqualTo(80));
|
||||
Assert.That(receiver.LastPayload.AttackPropertyType, Is.EqualTo(AttackPropertyType.Fire));
|
||||
Assert.That(receiver.LastPayload.SourceEntityId, Is.EqualTo(101));
|
||||
Assert.That(receiver.LastPayload.ProjectileEntityId, Is.EqualTo(202));
|
||||
Assert.That(receiver.LastPayload.OriginPosition, Is.EqualTo(new Vector3(1f, 2f, 0f)));
|
||||
Assert.That(receiver.LastPayload.TagRuntimes, Has.Length.EqualTo(1));
|
||||
Assert.That(receiver.LastPayload.TagRuntimes[0].TagType, Is.EqualTo(TagType.Fire));
|
||||
}
|
||||
@@ -600,6 +787,30 @@ namespace GeometryTD.Tests.EditMode
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void AttackPayload_Clone_Preserves_Source_Metadata()
|
||||
{
|
||||
AttackPayload payload = new AttackPayload
|
||||
{
|
||||
BaseDamage = 50,
|
||||
AttackPropertyType = AttackPropertyType.Physics,
|
||||
SourceEntityId = 11,
|
||||
ProjectileEntityId = 22,
|
||||
OriginPosition = new Vector3(3f, 4f, 0f),
|
||||
TagRuntimes = new[]
|
||||
{
|
||||
new TagRuntimeData { TagType = TagType.Crit, TotalStack = 1 }
|
||||
}
|
||||
};
|
||||
|
||||
AttackPayload cloned = payload.Clone();
|
||||
|
||||
Assert.That(cloned.SourceEntityId, Is.EqualTo(11));
|
||||
Assert.That(cloned.ProjectileEntityId, Is.EqualTo(22));
|
||||
Assert.That(cloned.OriginPosition, Is.EqualTo(new Vector3(3f, 4f, 0f)));
|
||||
Assert.That(cloned.TagRuntimes, Is.Not.SameAs(payload.TagRuntimes));
|
||||
}
|
||||
|
||||
private sealed class RecordingDamageReceiver : MonoBehaviour, IDamageReceiver
|
||||
{
|
||||
public AttackPayload LastPayload { get; private set; }
|
||||
|
||||
Reference in New Issue
Block a user