Files
tiny_tackle_heroes/Assets/_Project/Tests/EditMode/ArtifactGroggyEditModeRegressionTests.cs

368 lines
14 KiB
C#

using System;
using System.Collections.Generic;
using System.Reflection;
using BumpCombat.Combat;
using BumpCombat.Constants;
using BumpCombat.Core;
using BumpCombat.Enemies;
using BumpCombat.Player;
using NUnit.Framework;
using UnityEngine;
namespace BumpCombat.Tests
{
public sealed class ArtifactGroggyEditModeRegressionTests
{
private static readonly BindingFlags NonPublicInstance =
BindingFlags.Instance | BindingFlags.NonPublic;
private readonly List<UnityEngine.Object> createdObjects = new();
private GameObject owner;
[SetUp]
public void SetUp()
{
owner = new GameObject("Artifact Groggy EditMode Tests");
}
[TearDown]
public void TearDown()
{
if (owner != null)
{
UnityEngine.Object.DestroyImmediate(owner);
}
for (int i = createdObjects.Count - 1; i >= 0; i--)
{
if (createdObjects[i] != null)
{
UnityEngine.Object.DestroyImmediate(createdObjects[i]);
}
}
createdObjects.Clear();
}
[Test]
public void EnemyKindInfersGroggyTierAndContactRequirements()
{
EnemyController elite = CreateEnemy("Elite", EnemyKind.ArmoredSkeleton);
EnemyController midBoss = CreateEnemy(
"Mid Boss",
EnemyKind.GreatswordSkeleton);
EnemyController finalBoss = CreateEnemy(
"Final Boss",
EnemyKind.Necromancer);
Assert.That(elite.GroggyTier, Is.EqualTo(RunTimedEvent.Elite));
Assert.That(elite.ShieldHitRequirement, Is.EqualTo(1));
Assert.That(elite.GroggyRequiredContactCount, Is.EqualTo(1));
Assert.That(midBoss.GroggyTier, Is.EqualTo(RunTimedEvent.MidBoss));
Assert.That(midBoss.ShieldHitRequirement, Is.EqualTo(2));
Assert.That(midBoss.GroggyRequiredContactCount, Is.EqualTo(2));
Assert.That(finalBoss.GroggyTier, Is.EqualTo(RunTimedEvent.FinalBoss));
Assert.That(finalBoss.ShieldHitRequirement, Is.EqualTo(4));
Assert.That(finalBoss.GroggyRequiredContactCount, Is.EqualTo(4));
}
[Test]
public void EliteGroggyRequirement_ReadsChangedEnemySettings()
{
EnemyConstants constants = GameplayConstants.Current.Enemies;
int originalRequirement = constants.EliteGroggyRequiredMatchingHits;
try
{
constants.EliteGroggyRequiredMatchingHits = 3;
EnemyController enemy = CreateEnemy(
"Elite Changed Threshold",
EnemyKind.ArmoredSkeleton);
Assert.That(enemy.ShieldHitRequirement, Is.EqualTo(3));
Assert.That(enemy.GroggyRequiredContactCount, Is.EqualTo(3));
}
finally
{
constants.EliteGroggyRequiredMatchingHits = originalRequirement;
}
}
[Test]
public void NormalEnemyRemainsDamageableWithoutGroggyShield()
{
EnemyController enemy = CreateEnemy("Normal", EnemyKind.Skeleton);
float healthBefore = enemy.CurrentHealth;
Assert.That(enemy.IsGroggyTierGated, Is.False);
Assert.That(enemy.IsDamageInvulnerable, Is.False);
Assert.That(
enemy.TryTakeDamage(5f, Vector2.right, 0f, false),
Is.True);
Assert.That(enemy.CurrentHealth, Is.EqualTo(healthBefore - 5f));
Assert.That(
enemy.TryTakeArtifactHit(
7f,
Vector2.right,
0f,
ArtifactColor.Blue,
1,
false),
Is.EqualTo(EnemyArtifactHitResult.DamageApplied));
}
[Test]
public void GroggyShieldBlocksNormalArtifactAndDotDamageBeforeOpening()
{
EnemyController enemy = CreateEnemy("Elite Gate", EnemyKind.Werewolf);
float healthBefore = enemy.CurrentHealth;
Assert.That(enemy.IsDamageInvulnerable, Is.True);
Assert.That(
enemy.TryTakeDamage(5f, Vector2.right, 0f, false),
Is.False);
Assert.That(enemy.CurrentHealth, Is.EqualTo(healthBefore));
Assert.That(enemy.ApplyIgnite(1f, 0.5f, 4f), Is.True);
InvokeTimedEffects(enemy, 0.5f);
Assert.That(
enemy.CurrentHealth,
Is.EqualTo(healthBefore),
"A DOT tick must not bypass the groggy shield.");
Assert.That(enemy.GroggyQualifyingContactCount, Is.Zero);
Assert.That(
enemy.TryTakeArtifactHit(
9f,
Vector2.right,
0f,
ArtifactColor.Red,
1,
false),
Is.EqualTo(EnemyArtifactHitResult.Shielded));
Assert.That(enemy.LastAppliedDamage, Is.Zero);
Assert.That(enemy.CurrentHealth, Is.EqualTo(healthBefore));
Assert.That(enemy.IsGroggy, Is.False);
Assert.That(
enemy.TryTakeArtifactHit(
9f,
Vector2.right,
0f,
ArtifactColor.Green,
2,
false),
Is.EqualTo(EnemyArtifactHitResult.Shielded));
Assert.That(enemy.IsGroggy, Is.True);
Assert.That(enemy.IsDamageInvulnerable, Is.False);
Assert.That(
enemy.TryTakeDamage(5f, Vector2.right, 0f, false),
Is.True);
Assert.That(
enemy.TryTakeArtifactHit(
7f,
Vector2.right,
0f,
ArtifactColor.Blue,
3,
false),
Is.EqualTo(EnemyArtifactHitResult.DamageApplied));
float healthAfterDirectHits = enemy.CurrentHealth;
InvokeTimedEffects(enemy, 0.5f);
Assert.That(
enemy.CurrentHealth,
Is.LessThan(healthAfterDirectHits),
"The same DOT API is allowed to tick after groggy opens.");
}
[Test]
public void MidBossNeedsTwoMatchingColorContacts()
{
EnemyController enemy = CreateEnemy(
"Mid Boss Threshold",
EnemyKind.NecroGolem);
Assert.That(Contact(enemy, ArtifactColor.Green, 11), Is.EqualTo(EnemyArtifactHitResult.Shielded));
Assert.That(enemy.IsGroggy, Is.False);
Assert.That(enemy.GroggyQualifyingContactCount, Is.EqualTo(1));
Assert.That(
Contact(enemy, ArtifactColor.Green, 12),
Is.EqualTo(EnemyArtifactHitResult.Shielded));
Assert.That(enemy.IsGroggy, Is.True);
Assert.That(enemy.GroggyQualifyingContactCount, Is.EqualTo(2));
Assert.That(enemy.IsDamageInvulnerable, Is.False);
}
[Test]
public void FinalBossRequiresFourMatchingColorContacts()
{
EnemyController enemy = CreateEnemy(
"Final Boss Four Singles",
EnemyKind.Necromancer);
Assert.That(Contact(enemy, ArtifactColor.Red, 21), Is.EqualTo(EnemyArtifactHitResult.Shielded));
Assert.That(Contact(enemy, ArtifactColor.Blue, 22), Is.EqualTo(EnemyArtifactHitResult.Shielded));
Assert.That(Contact(enemy, ArtifactColor.Blue, 23), Is.EqualTo(EnemyArtifactHitResult.Shielded));
Assert.That(Contact(enemy, ArtifactColor.Red, 24), Is.EqualTo(EnemyArtifactHitResult.Shielded));
Assert.That(enemy.IsGroggy, Is.False);
Assert.That(enemy.IsDamageInvulnerable, Is.True);
Assert.That(enemy.ShieldHitsRemaining, Is.EqualTo(4));
Assert.That(enemy.GroggyQualifyingContactCount, Is.Zero);
Assert.That(Contact(enemy, ArtifactColor.Green, 25), Is.EqualTo(EnemyArtifactHitResult.Shielded));
Assert.That(Contact(enemy, ArtifactColor.Green, 26), Is.EqualTo(EnemyArtifactHitResult.Shielded));
Assert.That(Contact(enemy, ArtifactColor.Green, 27), Is.EqualTo(EnemyArtifactHitResult.Shielded));
Assert.That(Contact(enemy, ArtifactColor.Green, 28), Is.EqualTo(EnemyArtifactHitResult.Shielded));
Assert.That(enemy.IsGroggy, Is.True);
}
[Test]
public void FinalBossFourCastsOfOneColorOpenGroggy()
{
EnemyController enemy = CreateEnemy(
"Final Boss Four Green Activations",
EnemyKind.Necromancer);
Assert.That(Contact(enemy, ArtifactColor.Green, 31), Is.EqualTo(EnemyArtifactHitResult.Shielded));
Assert.That(Contact(enemy, ArtifactColor.Green, 32), Is.EqualTo(EnemyArtifactHitResult.Shielded));
Assert.That(Contact(enemy, ArtifactColor.Green, 33), Is.EqualTo(EnemyArtifactHitResult.Shielded));
Assert.That(
Contact(enemy, ArtifactColor.Green, 34),
Is.EqualTo(EnemyArtifactHitResult.Shielded));
Assert.That(enemy.IsGroggy, Is.True);
Assert.That(enemy.GroggyQualifyingContactCount, Is.EqualTo(4));
}
[Test]
public void SameCastMultiTickIsDeduplicatedPerEnemy()
{
EnemyController enemy = CreateEnemy(
"Mid Boss Dedup",
EnemyKind.GreatswordSkeleton);
Assert.That(Contact(enemy, ArtifactColor.Green, 41), Is.EqualTo(EnemyArtifactHitResult.Shielded));
Assert.That(
Contact(enemy, ArtifactColor.Green, 41),
Is.EqualTo(EnemyArtifactHitResult.Rejected));
Assert.That(enemy.GroggyQualifyingContactCount, Is.EqualTo(1));
Assert.That(enemy.IsGroggy, Is.False);
Assert.That(Contact(enemy, ArtifactColor.Green, 42), Is.EqualTo(EnemyArtifactHitResult.Shielded));
Assert.That(enemy.IsGroggy, Is.True);
}
[Test]
public void GroggyHitsDoNotExtendOrPrechargeTheNextCycle()
{
EnemyController enemy = CreateEnemy("Groggy Rearm", EnemyKind.Werebear);
Assert.That(Contact(enemy, ArtifactColor.Green, 51), Is.EqualTo(EnemyArtifactHitResult.Shielded));
float remaining = enemy.GroggyTimeRemaining;
Assert.That(
Contact(enemy, ArtifactColor.Blue, 52),
Is.EqualTo(EnemyArtifactHitResult.DamageApplied));
Assert.That(enemy.GroggyTimeRemaining, Is.EqualTo(remaining).Within(0.001f));
Assert.That(enemy.ApplyStun(2f), Is.False);
Assert.That(enemy.IsStunned, Is.False);
enemy.EndGroggyForProtection();
Assert.That(enemy.IsGroggy, Is.False);
Assert.That(enemy.IsDamageInvulnerable, Is.True);
Assert.That(enemy.GroggyQualifyingContactCount, Is.Zero);
Assert.That(
enemy.RegisterArtifactContact(ArtifactColor.Blue, 52),
Is.EqualTo(EnemyArtifactContactResult.Ignored),
"A cast that first contacted during groggy must not precharge the next shield.");
Assert.That(
enemy.RegisterArtifactContact(ArtifactColor.Red, 53),
Is.EqualTo(EnemyArtifactContactResult.Shielded));
Assert.That(enemy.IsGroggy, Is.True);
Assert.That(enemy.GroggyQualifyingContactCount, Is.EqualTo(1));
}
private EnemyController CreateEnemy(string name, EnemyKind kind)
{
EnemyDefinition definition =
ScriptableObject.CreateInstance<EnemyDefinition>();
definition.Configure(
kind,
EnemyAttackShape.Body,
100f,
0f,
0f,
0f,
0f,
0f,
0f,
0f,
0f,
0f,
0f,
0);
createdObjects.Add(definition);
GameObject enemyObject = new(name);
enemyObject.transform.SetParent(owner.transform);
enemyObject.AddComponent<Rigidbody2D>();
enemyObject.AddComponent<CircleCollider2D>();
enemyObject.AddComponent<SpriteRenderer>();
EnemyController enemy = enemyObject.AddComponent<EnemyController>();
enemy.Configure(definition, null);
EnemyAttack enemyAttack = enemyObject.GetComponent<EnemyAttack>();
enemyAttack.GetType()
.GetMethod("Awake", NonPublicInstance)
?.Invoke(enemyAttack, null);
enemy.GetType()
.GetMethod("Awake", NonPublicInstance)
?.Invoke(enemy, null);
SetAutoProperty(enemy, "CurrentHealth", 100f);
return enemy;
}
private static EnemyArtifactHitResult Contact(
EnemyController enemy,
ArtifactColor artifactColor,
int castIdentity)
{
return enemy.TryTakeArtifactHit(
10f,
Vector2.right,
0f,
artifactColor,
castIdentity,
false);
}
private static void InvokeTimedEffects(
EnemyController enemy,
float deltaTime)
{
MethodInfo method = typeof(EnemyController).GetMethod(
"UpdateTimedEffects",
NonPublicInstance);
Assert.That(method, Is.Not.Null);
method.Invoke(enemy, new object[] { deltaTime });
}
private static void SetAutoProperty(
object target,
string propertyName,
object value)
{
FieldInfo field = target.GetType().GetField(
$"<{propertyName}>k__BackingField",
NonPublicInstance);
Assert.That(field, Is.Not.Null, $"Missing {propertyName} backing field.");
field.SetValue(target, value);
}
}
}