2.5d test
This commit is contained in:
@@ -0,0 +1,351 @@
|
||||
using System.Collections.Generic;
|
||||
using BumpCombat.Combat;
|
||||
using BumpCombat.Constants;
|
||||
using BumpCombat.Defense.Comparison;
|
||||
using BumpCombat.Enemies;
|
||||
using BumpCombat.Player;
|
||||
using BumpCombat.Progression;
|
||||
using NUnit.Framework;
|
||||
using UnityEngine;
|
||||
using Object = UnityEngine.Object;
|
||||
|
||||
namespace BumpCombat.Tests
|
||||
{
|
||||
public sealed class Defense25DRelicRuntimeTests
|
||||
{
|
||||
private PassiveRelicConstants tuning;
|
||||
private Defense25DLoadout loadout;
|
||||
private readonly List<ActiveArtifactDefinition> artifactDefinitions = new();
|
||||
|
||||
[SetUp]
|
||||
public void SetUp()
|
||||
{
|
||||
tuning = ScriptableObject.CreateInstance<PassiveRelicConstants>();
|
||||
loadout = new Defense25DLoadout(0f, 100f, 6, 6);
|
||||
}
|
||||
|
||||
[TearDown]
|
||||
public void TearDown()
|
||||
{
|
||||
Object.DestroyImmediate(tuning);
|
||||
foreach (ActiveArtifactDefinition definition in artifactDefinitions)
|
||||
Object.DestroyImmediate(definition);
|
||||
artifactDefinitions.Clear();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TwinHornOnlyDoublesOrdinaryUncoloredBumpsWhenTheConfiguredRollSucceeds()
|
||||
{
|
||||
tuning.TwinHornDoubleDamageChance = 1f;
|
||||
loadout.TryAcquireRelic(PassiveRelicId.TwinHornTalisman);
|
||||
Defense25DRelicRuntime runtime = NewRuntime(new Defense25DRelicEffects());
|
||||
|
||||
Assert.That(runtime.ApplyOrdinaryBumpDamage(10f, true, false), Is.EqualTo(20f));
|
||||
Assert.That(runtime.ApplyOrdinaryBumpDamage(10f, false, false), Is.EqualTo(10f));
|
||||
Assert.That(runtime.ApplyOrdinaryBumpDamage(10f, true, true), Is.EqualTo(10f));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void WaveNailAppliesSourceDamageToNearbyEnemiesAndStartsCooldownOnlyOnAppliedHit()
|
||||
{
|
||||
tuning.WaveNailChance = 1f;
|
||||
tuning.WaveNailDamageFraction = .6f;
|
||||
tuning.WaveNailCooldownSeconds = 1.5f;
|
||||
loadout.TryAcquireRelic(PassiveRelicId.WaveNail);
|
||||
int applied = 0;
|
||||
int attempts = 0;
|
||||
bool allowDamage = false;
|
||||
float observedDamage = 0f;
|
||||
EnemyDamageSource observedSource = EnemyDamageSource.None;
|
||||
Defense25DRelicTarget nearby = Target(2, new Vector3(1f, 0f, 0f));
|
||||
Defense25DRelicEffects effects = new()
|
||||
{
|
||||
QueryEnemies = (_, _) => new[] { nearby },
|
||||
DamageEnemy = (_, damage, _, source) =>
|
||||
{
|
||||
attempts++;
|
||||
observedDamage = damage;
|
||||
observedSource = source;
|
||||
if (!allowDamage) return false;
|
||||
applied++;
|
||||
return true;
|
||||
},
|
||||
};
|
||||
Defense25DRelicRuntime runtime = NewRuntime(effects);
|
||||
runtime.BodyDiameterProvider = () => 1f;
|
||||
runtime.NotifyHit(new Defense25DRelicHit(Target(1, Vector3.zero), Vector3.zero,
|
||||
Vector3.right, 10f, 10f, true), 100f);
|
||||
allowDamage = true;
|
||||
runtime.NotifyHit(new Defense25DRelicHit(Target(0, Vector3.zero), Vector3.zero,
|
||||
Vector3.right, 10f, 10f, true), 100f);
|
||||
runtime.Advance(1f, 100f);
|
||||
runtime.NotifyHit(new Defense25DRelicHit(Target(0, Vector3.zero), Vector3.zero,
|
||||
Vector3.right, 10f, 10f, true), 100f);
|
||||
|
||||
Assert.That(applied, Is.EqualTo(1));
|
||||
Assert.That(attempts, Is.EqualTo(2));
|
||||
Assert.That(observedDamage, Is.EqualTo(6f));
|
||||
Assert.That(observedSource, Is.EqualTo(EnemyDamageSource.PassiveRelicAttack));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void CounterBellSlowsEveryLivingEnemyInItsQueryInsteadOfStoppingAtFirstSuccess()
|
||||
{
|
||||
tuning.CounterBellChance = 1f;
|
||||
loadout.TryAcquireRelic(PassiveRelicId.CounterBell);
|
||||
List<int> slowed = new();
|
||||
Defense25DRelicTarget[] targets =
|
||||
{
|
||||
Target(1, Vector3.zero),
|
||||
Target(2, Vector3.right),
|
||||
new(3, Vector3.forward, 10f, 10f, EnemyDamageSource.None, true),
|
||||
};
|
||||
Defense25DRelicRuntime runtime = NewRuntime(new Defense25DRelicEffects
|
||||
{
|
||||
QueryEnemies = (_, _) => targets,
|
||||
ApplySlow = (target, _, _) => { slowed.Add(target.Identity); return true; },
|
||||
});
|
||||
runtime.BodyDiameterProvider = () => 1f;
|
||||
|
||||
runtime.NotifyPlayerDamageTaken(1f, 99f, 100f, Vector3.zero);
|
||||
|
||||
Assert.That(slowed, Is.EquivalentTo(new[] { 1, 2 }));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void LethalValidHitStillRunsWaveNailBloodFangAndResonanceCore()
|
||||
{
|
||||
tuning.WaveNailChance = 1f;
|
||||
tuning.ResonanceEchoChance = 1f;
|
||||
tuning.BloodFangChance = 1f;
|
||||
loadout.TryAcquireRelic(PassiveRelicId.WaveNail);
|
||||
loadout.TryAcquireRelic(PassiveRelicId.BloodFang);
|
||||
loadout.TryAcquireRelic(PassiveRelicId.ResonanceCore);
|
||||
ActiveArtifactDefinition artifact = Artifact(ActiveArtifactEffect.Pulse);
|
||||
Assert.That(loadout.TryAddArtifact(artifact), Is.True);
|
||||
int damageCalls = 0, healCalls = 0, echoCalls = 0;
|
||||
Defense25DRelicTarget nearby = Target(11, Vector3.right);
|
||||
Defense25DRelicRuntime runtime = NewRuntime(new Defense25DRelicEffects
|
||||
{
|
||||
QueryEnemies = (_, _) => new[] { nearby },
|
||||
DamageEnemy = (_, _, _, _) => { damageCalls++; return true; },
|
||||
HealPlayer = _ => { healCalls++; return true; },
|
||||
EmitEcho = (use, _, _, _) =>
|
||||
{
|
||||
Assert.That(use.Definition, Is.SameAs(artifact));
|
||||
echoCalls++;
|
||||
return true;
|
||||
},
|
||||
});
|
||||
runtime.BodyDiameterProvider = () => 1f;
|
||||
runtime.NotifyHit(new Defense25DRelicHit(
|
||||
Target(10, Vector3.zero, isDead: true), Vector3.zero, Vector3.right,
|
||||
10f, 10f, true), 50f, 100f);
|
||||
|
||||
Assert.That(damageCalls, Is.EqualTo(1));
|
||||
Assert.That(healCalls, Is.EqualTo(1));
|
||||
Assert.That(echoCalls, Is.EqualTo(1));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void WaveNailCanDamageTheOriginalHitTargetWhenItSurvivesTheBump()
|
||||
{
|
||||
tuning.WaveNailChance = 1f;
|
||||
loadout.TryAcquireRelic(PassiveRelicId.WaveNail);
|
||||
List<int> damaged = new();
|
||||
Defense25DRelicTarget struck = Target(0, Vector3.zero);
|
||||
Defense25DRelicTarget neighbor = Target(1, Vector3.right);
|
||||
Defense25DRelicRuntime runtime = NewRuntime(new Defense25DRelicEffects
|
||||
{
|
||||
QueryEnemies = (_, _) => new[] { struck, neighbor },
|
||||
DamageEnemy = (target, _, _, _) => { damaged.Add(target.Identity); return true; },
|
||||
});
|
||||
runtime.BodyDiameterProvider = () => 1f;
|
||||
|
||||
runtime.NotifyHit(new Defense25DRelicHit(struck, Vector3.zero, Vector3.right,
|
||||
10f, 10f, true), 100f);
|
||||
|
||||
Assert.That(damaged, Is.EquivalentTo(new[] { 0, 1 }));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void WaveNailIncludesAnEnemyWhoseBodyOverlapsTheAreaAtItsOuterEdge()
|
||||
{
|
||||
tuning.WaveNailChance = 1f;
|
||||
loadout.TryAcquireRelic(PassiveRelicId.WaveNail);
|
||||
List<int> damaged = new();
|
||||
Defense25DRelicTarget boundaryTarget = Target(12, new Vector3(3.2f, 0f, 0f));
|
||||
const float targetBodyRadius = .3f;
|
||||
Defense25DRelicRuntime runtime = NewRuntime(new Defense25DRelicEffects
|
||||
{
|
||||
QueryEnemies = (center, areaRadius) =>
|
||||
Vector3.Distance(new Vector3(center.x, 0f, center.z),
|
||||
new Vector3(boundaryTarget.Position.x, 0f, boundaryTarget.Position.z))
|
||||
<= areaRadius + targetBodyRadius
|
||||
? new[] { boundaryTarget }
|
||||
: System.Array.Empty<Defense25DRelicTarget>(),
|
||||
DamageEnemy = (target, _, _, _) => { damaged.Add(target.Identity); return true; },
|
||||
});
|
||||
runtime.BodyDiameterProvider = () => 1f;
|
||||
|
||||
runtime.NotifyHit(new Defense25DRelicHit(Target(0, Vector3.zero), Vector3.zero,
|
||||
Vector3.right, 10f, 10f, true), 100f);
|
||||
|
||||
Assert.That(damaged, Is.EqualTo(new[] { boundaryTarget.Identity }));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void RiftWedgeAppliesThunderCrashVulnerabilityOnTheConfiguredOrdinaryHitCount()
|
||||
{
|
||||
tuning.RiftWedgeRequiredHits = 3;
|
||||
loadout.TryAcquireRelic(PassiveRelicId.RiftWedge);
|
||||
ActiveArtifactDefinition thunder = Artifact(ActiveArtifactEffect.ThunderCrash,
|
||||
normalVulnerabilityDuration: 2.5f, normalVulnerabilityIncrease: .3f);
|
||||
loadout.ConfigureCatalog(new[] { thunder });
|
||||
int applied = 0;
|
||||
Defense25DRelicEffects effects = new()
|
||||
{
|
||||
ApplyVulnerability = (_, duration, increase) =>
|
||||
{
|
||||
Assert.That(duration, Is.EqualTo(2.5f));
|
||||
Assert.That(increase, Is.EqualTo(.3f));
|
||||
applied++;
|
||||
return true;
|
||||
},
|
||||
};
|
||||
Defense25DRelicRuntime runtime = NewRuntime(effects);
|
||||
Defense25DRelicTarget target = Target(7, Vector3.zero);
|
||||
|
||||
for (int i = 0; i < 2; i++)
|
||||
runtime.NotifyHit(new Defense25DRelicHit(target, Vector3.zero, Vector3.right,
|
||||
1f, 0f, true), 100f);
|
||||
Assert.That(applied, Is.Zero);
|
||||
runtime.NotifyHit(new Defense25DRelicHit(target, Vector3.zero, Vector3.right,
|
||||
1f, 0f, true), 100f);
|
||||
|
||||
Assert.That(applied, Is.EqualTo(1));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void BondLedgerPaysOnlyForTowerKillsAfterRecentBumpAndDeduplicatesDeaths()
|
||||
{
|
||||
tuning.BondLedgerChance = 1f;
|
||||
tuning.BondLedgerCurrency = 2;
|
||||
tuning.BondLedgerBumpWindowSeconds = 3f;
|
||||
loadout.TryAcquireRelic(PassiveRelicId.BondLedger);
|
||||
int currency = 0;
|
||||
Defense25DRelicRuntime runtime = NewRuntime(new Defense25DRelicEffects
|
||||
{
|
||||
AwardCurrency = amount => { currency += amount; return true; },
|
||||
});
|
||||
Defense25DRelicTarget target = Target(0, Vector3.zero);
|
||||
runtime.NotifyHit(new Defense25DRelicHit(target, Vector3.zero, Vector3.right,
|
||||
1f, 0f, true), 100f);
|
||||
runtime.Advance(1f, 100f);
|
||||
Defense25DRelicTarget towerKill = Target(0, Vector3.zero, EnemyDamageSource.Tower);
|
||||
|
||||
runtime.NotifyEnemyDeath(towerKill, 100f);
|
||||
runtime.NotifyEnemyDeath(towerKill, 100f);
|
||||
|
||||
Assert.That(currency, Is.EqualTo(2));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void RunResetClearsRuntimeTimersAndTemporaryModifiersThroughOwnerCallbacks()
|
||||
{
|
||||
tuning.HunterFeatherChance = 1f;
|
||||
loadout.TryAcquireRelic(PassiveRelicId.HunterFeather);
|
||||
int added = 0, removed = 0;
|
||||
Defense25DRelicRuntime runtime = NewRuntime(new Defense25DRelicEffects
|
||||
{
|
||||
SetTimedPlayerModifier = (_, _) => added++,
|
||||
RemovePlayerModifier = _ => removed++,
|
||||
});
|
||||
runtime.NotifyEnemyDeath(Target(8, Vector3.zero, EnemyDamageSource.Player), 100f);
|
||||
Assert.That(runtime.HunterExpiresAt, Is.GreaterThan(0f));
|
||||
|
||||
runtime.ResetRun(4f);
|
||||
|
||||
Assert.That(runtime.IsActive, Is.False);
|
||||
Assert.That(runtime.HunterExpiresAt, Is.Zero);
|
||||
Assert.That(added, Is.EqualTo(1));
|
||||
Assert.That(removed, Is.GreaterThanOrEqualTo(1));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ResonantCrystalRepeatExpiresIfItsDelayCompletesWhilePlayerIsDead()
|
||||
{
|
||||
tuning.ResonantCrystalChance = 1f;
|
||||
tuning.ResonantCrystalDelaySeconds = 1f;
|
||||
ActiveArtifactDefinition artifact = Artifact(ActiveArtifactEffect.Cyclone);
|
||||
Assert.That(loadout.TryAddArtifact(artifact), Is.True);
|
||||
Assert.That(loadout.TryAcquireRelic(PassiveRelicId.ResonantCrystal), Is.True);
|
||||
int repeats = 0;
|
||||
Defense25DRelicRuntime runtime = NewRuntime(new Defense25DRelicEffects
|
||||
{
|
||||
EmitRepeat = (_, _, _, _) => { repeats++; return true; },
|
||||
});
|
||||
|
||||
runtime.NotifyArtifactUse(new Defense25DArtifactUse(artifact, false),
|
||||
Vector3.zero, Vector3.right, 100f);
|
||||
Assert.That(runtime.IsRepeatPending, Is.True);
|
||||
|
||||
runtime.Advance(1f, 0f);
|
||||
runtime.Advance(2f, 100f);
|
||||
|
||||
Assert.That(runtime.IsRepeatPending, Is.False);
|
||||
Assert.That(repeats, Is.Zero);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ResonantCrystalRepeatStillFiresAfterDeathIfDelayHasNotEndedYet()
|
||||
{
|
||||
tuning.ResonantCrystalChance = 1f;
|
||||
tuning.ResonantCrystalDelaySeconds = 1f;
|
||||
ActiveArtifactDefinition artifact = Artifact(ActiveArtifactEffect.Cyclone);
|
||||
Assert.That(loadout.TryAddArtifact(artifact), Is.True);
|
||||
Assert.That(loadout.TryAcquireRelic(PassiveRelicId.ResonantCrystal), Is.True);
|
||||
int repeats = 0;
|
||||
Defense25DRelicRuntime runtime = NewRuntime(new Defense25DRelicEffects
|
||||
{
|
||||
EmitRepeat = (_, _, _, _) => { repeats++; return true; },
|
||||
});
|
||||
|
||||
runtime.NotifyArtifactUse(new Defense25DArtifactUse(artifact, false),
|
||||
Vector3.zero, Vector3.right, 100f);
|
||||
runtime.Advance(.5f, 0f);
|
||||
Assert.That(runtime.IsRepeatPending, Is.True);
|
||||
|
||||
runtime.Advance(1f, 100f);
|
||||
|
||||
Assert.That(runtime.IsRepeatPending, Is.False);
|
||||
Assert.That(repeats, Is.EqualTo(1));
|
||||
}
|
||||
|
||||
private Defense25DRelicRuntime NewRuntime(Defense25DRelicEffects effects)
|
||||
{
|
||||
Defense25DRelicRuntime runtime = new(loadout, tuning, effects, () => 0f);
|
||||
runtime.BeginRun();
|
||||
return runtime;
|
||||
}
|
||||
|
||||
private ActiveArtifactDefinition Artifact(
|
||||
ActiveArtifactEffect effect, float normalVulnerabilityDuration = 0f,
|
||||
float normalVulnerabilityIncrease = 0f)
|
||||
{
|
||||
ActiveArtifactDefinition definition = ScriptableObject.CreateInstance<ActiveArtifactDefinition>();
|
||||
definition.Configure(effect.ToString(), effect.ToString(), "*", effect,
|
||||
DamageTag.Collision, Color.white, Color.white, 0f, 0f, 1f,
|
||||
1f, 1f, 1f, 1f, 1f, 1f,
|
||||
normalBumpVulnerabilityIncrease: normalVulnerabilityIncrease,
|
||||
normalBumpVulnerabilityDuration: normalVulnerabilityDuration);
|
||||
artifactDefinitions.Add(definition);
|
||||
return definition;
|
||||
}
|
||||
|
||||
private static Defense25DRelicTarget Target(int id, Vector3 position,
|
||||
EnemyDamageSource source = EnemyDamageSource.None, bool isDead = false) =>
|
||||
new(id, position, 100f, 100f, source, isDead);
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user