368 lines
14 KiB
C#
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);
|
|
}
|
|
}
|
|
}
|