using BumpCombat.Constants; using BumpCombat.Defense; using BumpCombat.Defense.Comparison; using NUnit.Framework; namespace BumpCombat.PlayModeTests { public sealed class Defense25DTowerModifierPlayModeTests { [Test] public void AttackAndAreaStacksShareCapsAcrossRaritiesAndSightIsAppliedOnce() { Defense25DRewardConstants constants = UnityEngine.ScriptableObject.CreateInstance(); try { Defense25DTowerModifiers modifiers = new(constants); Defense25DTowerModifierDefinition commonAttack = Find( constants, "fire.attack.common"); Defense25DTowerModifierDefinition rareAttack = Find( constants, "fire.attack.rare"); Defense25DTowerModifierDefinition commonArea = Find( constants, "fire.area.common"); for (int i = 0; i < 5; i++) { Assert.That(modifiers.Apply( Defense25DRewardChoice.CreateTowerModifier(commonAttack)), Is.True); } Assert.That(modifiers.Apply( Defense25DRewardChoice.CreateTowerModifier(rareAttack)), Is.False, "Rarity variants share the family/effect stack cap."); for (int i = 0; i < 3; i++) { Assert.That(modifiers.Apply( Defense25DRewardChoice.CreateTowerModifier(commonArea)), Is.True); } Assert.That(modifiers.GetStackCount( DefenseTowerKind.Direct, Defense25DTowerModifierEffect.AreaRadiusIncreased), Is.EqualTo(3)); Defense25DTowerStatInput tower = new( DefenseTowerKind.Direct, baseDamage: 100f, baseAreaRadius: 2f, hasAreaEffect: true, tierDamageMultiplier: 1.2f, supportDamageMultiplier: 1.1f, relicDamageMultiplier: 1.15f, tierAreaMultiplier: 1f, relicAreaMultiplier: 1f, sightEfficiency: 0.7f); Defense25DTowerEffectiveStats effective = modifiers.GetEffectiveStats(tower); Assert.That(effective.DamageBeforeSight, Is.EqualTo(100f * 1.2f * 1.1f * 1.15f * 1.5f).Within(0.001f)); Assert.That(effective.Damage, Is.EqualTo(effective.DamageBeforeSight * 0.7f).Within(0.001f)); Assert.That(effective.AreaRadius, Is.EqualTo(2f * 1.3f).Within(0.001f)); } finally { UnityEngine.Object.DestroyImmediate(constants); } } [Test] public void ProjectileEffectsApplyOnlyToEligibleColdProjectilesAndResetClearsRunState() { Defense25DRewardConstants constants = UnityEngine.ScriptableObject.CreateInstance(); try { Defense25DTowerModifiers modifiers = new(constants); Assert.That(modifiers.Apply(Defense25DRewardChoice.CreateTowerModifier( Find(constants, "cold.projectile.epic"))), Is.True); Assert.That(modifiers.Apply(Defense25DRewardChoice.CreateTowerModifier( Find(constants, "cold.split.epic"))), Is.True); Assert.That(modifiers.Apply(Defense25DRewardChoice.CreateTowerModifier( Find(constants, "cold.return.epic"))), Is.True); Defense25DTowerEffectiveStats coldBolt = modifiers.GetEffectiveStats( new Defense25DTowerStatInput( DefenseTowerKind.Cold, baseDamage: 10f, baseAreaRadius: 0f, isProjectileAttack: true)); Assert.That(coldBolt.ProjectileCount, Is.EqualTo(2)); Assert.That(coldBolt.SplitsOnFirstValidHit, Is.True); Assert.That(coldBolt.ReturnsOnceToOrigin, Is.True); Defense25DTowerEffectiveStats blizzard = modifiers.GetEffectiveStats( new Defense25DTowerStatInput( DefenseTowerKind.Cold, baseDamage: 10f, baseAreaRadius: 2f, hasAreaEffect: true, isProjectileAttack: false)); Assert.That(blizzard.ProjectileCount, Is.EqualTo(1)); Assert.That(blizzard.SplitsOnFirstValidHit, Is.False); Assert.That(blizzard.ReturnsOnceToOrigin, Is.False); modifiers.Reset(); Assert.That(modifiers.AppliedChoiceCount, Is.Zero); Assert.That(modifiers.GetEffectiveStats(coldBoltInput()).ProjectileCount, Is.EqualTo(1)); } finally { UnityEngine.Object.DestroyImmediate(constants); } } private static Defense25DTowerStatInput coldBoltInput() { return new Defense25DTowerStatInput( DefenseTowerKind.Cold, baseDamage: 10f, baseAreaRadius: 0f, isProjectileAttack: true); } private static Defense25DTowerModifierDefinition Find( Defense25DRewardConstants constants, string definitionId) { foreach (Defense25DTowerModifierDefinition definition in constants.TowerModifierCatalog) { if (definition.DefinitionId == definitionId) { return definition; } } Assert.Fail($"Missing test tower modifier '{definitionId}'."); return null; } } }