Files
tiny_tackle_heroes/Assets/_Project/Tests/PlayMode/ArtifactChargeVisualPlayModeTests.cs

572 lines
24 KiB
C#

using System.Collections;
using System.Reflection;
using BumpCombat.Combat;
using BumpCombat.Core;
using BumpCombat.Enemies;
using BumpCombat.Player;
using BumpCombat.Progression;
using BumpCombat.Spawning;
using NUnit.Framework;
using UnityEngine;
using UnityEngine.InputSystem;
using UnityEngine.InputSystem.LowLevel;
using UnityEngine.SceneManagement;
using UnityEngine.TestTools;
namespace BumpCombat.PlayModeTests
{
public sealed class ArtifactChargeVisualPlayModeTests
{
private Keyboard virtualKeyboard;
private bool virtualKeyboardInputActive;
private InputSettings.BackgroundBehavior previousBackgroundBehavior;
private InputSettings.EditorInputBehaviorInPlayMode previousEditorInputBehavior;
private bool previousRunInBackground;
private bool inputSettingsCaptured;
[SetUp]
public void SetUp()
{
RunManager.ForceProductionModeForTests = false;
ArtifactRewardController.GrantCatalogForTests = true;
Time.timeScale = 1f;
}
[TearDown]
public void TearDown()
{
if (virtualKeyboardInputActive
&& virtualKeyboard != null
&& virtualKeyboard.added)
{
InputSystem.QueueStateEvent(virtualKeyboard, new KeyboardState());
InputSystem.RemoveDevice(virtualKeyboard);
}
virtualKeyboard = null;
virtualKeyboardInputActive = false;
if (inputSettingsCaptured)
{
InputSystem.settings.backgroundBehavior = previousBackgroundBehavior;
InputSystem.settings.editorInputBehaviorInPlayMode =
previousEditorInputBehavior;
Application.runInBackground = previousRunInBackground;
inputSettingsCaptured = false;
}
ArtifactRewardController.GrantCatalogForTests = false;
RunManager.ForceProductionModeForTests = false;
Time.timeScale = 1f;
}
[UnityTest]
public IEnumerator Switching_ShowsDestinationBurstOnceAndCleansUp()
{
yield return LoadCombatPrototype();
ActiveArtifactController artifacts = Object.FindAnyObjectByType<ActiveArtifactController>();
ArtifactChargeVisual visual = artifacts.GetComponent<ArtifactChargeVisual>();
Object.FindAnyObjectByType<SpawnDirector>().enabled = false;
foreach (EnemyController enemy in Object.FindObjectsByType<EnemyController>(FindObjectsSortMode.None))
{
enemy.gameObject.SetActive(false);
}
Assert.That(visual.IsSwitchVisible, Is.False, "Initial acquisition is not a switch.");
float gauge = artifacts.CurrentGauge;
for (int i = 0; i < artifacts.OwnedArtifactCount; i++)
{
Assert.That(artifacts.SelectNext(), Is.True);
Assert.That(visual.IsSwitchVisible, Is.True);
SpriteRenderer burst = visual.SwitchObject.GetComponent<SpriteRenderer>();
Assert.That(burst.sprite.texture.name,
Is.EqualTo(artifacts.CurrentArtifact.Effect + "-Release-v1"));
Assert.That(burst.color, Is.EqualTo(Color.white));
Assert.That(visual.SwitchObject.transform.parent, Is.EqualTo(artifacts.transform));
yield return null;
int bursts = 0;
foreach (SpriteRenderer renderer in artifacts.GetComponentsInChildren<SpriteRenderer>())
{
if (renderer.name == "Artifact Switch") bursts++;
}
Assert.That(bursts, Is.EqualTo(1), "Rapid switches replace the previous burst.");
}
Assert.That(artifacts.CurrentGauge, Is.EqualTo(gauge));
GameObject activeBurst = visual.SwitchObject;
Sprite pausedFrame = activeBurst.GetComponent<SpriteRenderer>().sprite;
Time.timeScale = 0f;
yield return new WaitForSecondsRealtime(0.4f);
Assert.That(visual.SwitchObject, Is.EqualTo(activeBurst));
Assert.That(activeBurst.GetComponent<SpriteRenderer>().sprite, Is.EqualTo(pausedFrame));
Time.timeScale = 1f;
yield return new WaitForSeconds(ArtifactChargeVisual.SwitchVisualDuration + 0.05f);
Assert.That(visual.IsSwitchVisible, Is.False);
Assert.That(artifacts.SelectNext(), Is.True);
visual.enabled = false;
Assert.That(visual.IsSwitchVisible, Is.False);
visual.enabled = true;
Assert.That(visual.IsSwitchVisible, Is.False);
}
[UnityTest]
public IEnumerator AInput_ChargeVisualsFollowReadyFlashAndReleaseOnce()
{
yield return LoadCombatPrototype();
SpawnDirector director = Object.FindAnyObjectByType<SpawnDirector>();
if (director != null)
{
director.enabled = false;
}
foreach (EnemyController enemy in Object.FindObjectsByType<EnemyController>(
FindObjectsSortMode.None))
{
enemy.gameObject.SetActive(false);
}
Keyboard keyboard = BeginVirtualKeyboardInput();
PlayerController player = Object.FindAnyObjectByType<PlayerController>();
ActiveArtifactController artifacts =
player.GetComponent<ActiveArtifactController>();
Rigidbody2D body = player.GetComponent<Rigidbody2D>();
ArtifactChargeVisual visual =
player.GetComponent<ArtifactChargeVisual>();
Assert.That(visual, Is.Not.Null);
SpriteRenderer playerRenderer = player.GetComponent<SpriteRenderer>();
Assert.That(playerRenderer, Is.Not.Null);
ActiveArtifactDefinition definition = SelectArtifact(
artifacts,
ActiveArtifactEffect.Dash);
EnsureGauge(artifacts, definition.ChargedGaugeCost);
Vector2 start = player.transform.position;
InputSystem.QueueStateEvent(keyboard, new KeyboardState(Key.A));
yield return null;
Assert.That(keyboard.aKey.isPressed, Is.True);
Assert.That(visual.IsAuraVisible, Is.True);
SpriteRenderer auraRenderer =
visual.AuraObject.GetComponent<SpriteRenderer>();
Assert.That(auraRenderer.sortingLayerID,
Is.EqualTo(playerRenderer.sortingLayerID));
Assert.That(auraRenderer.sortingOrder,
Is.EqualTo(playerRenderer.sortingOrder - 1));
Assert.That(
visual.AuraObject.transform.position,
Is.EqualTo((Vector3)(start + Vector2.up * ArtifactChargeVisual.ChargeVisualY)));
body.position = start + Vector2.right;
Physics2D.SyncTransforms();
yield return null;
Vector2 movedPlayerPosition = player.transform.position;
Assert.That(
visual.AuraObject.transform.position,
Is.EqualTo((Vector3)(movedPlayerPosition
+ Vector2.up * ArtifactChargeVisual.ChargeVisualY)));
yield return new WaitForSeconds(definition.ChargeDuration + 0.05f);
Assert.That(visual.IsReadyAuraVisible, Is.True);
Assert.That(visual.IsAuraVisible, Is.False);
Assert.That(visual.FlashPlayCount, Is.EqualTo(1));
yield return null;
Assert.That(visual.FlashPlayCount, Is.EqualTo(1));
InputSystem.QueueStateEvent(keyboard, new KeyboardState());
yield return null;
Assert.That(keyboard.aKey.isPressed, Is.False);
Vector2 releaseOrigin = artifacts.LastArtifactUsePosition;
Assert.That(visual.IsReadyAuraVisible, Is.False);
Assert.That(visual.ReleasePlayCount, Is.EqualTo(1));
Assert.That(visual.IsReleaseVisible, Is.True);
SpriteRenderer releaseRenderer =
visual.ReleaseObject.GetComponent<SpriteRenderer>();
Assert.That(releaseRenderer.sortingLayerID,
Is.EqualTo(playerRenderer.sortingLayerID));
Assert.That(releaseRenderer.sortingOrder,
Is.EqualTo(playerRenderer.sortingOrder + 1));
Assert.That(
visual.ReleaseObject.transform.position,
Is.EqualTo((Vector3)(releaseOrigin
+ Vector2.up * ArtifactChargeVisual.ChargeVisualY)));
body.position = releaseOrigin + Vector2.left;
Physics2D.SyncTransforms();
yield return null;
Assert.That(
visual.ReleaseObject.transform.position,
Is.EqualTo((Vector3)(releaseOrigin
+ Vector2.up * ArtifactChargeVisual.ChargeVisualY)));
Sprite pausedReleaseFrame = releaseRenderer.sprite;
Assert.That(RunManager.Instance.TryOpenPause(), Is.True);
yield return new WaitForSecondsRealtime(0.18f);
Assert.That(visual.IsReleaseVisible, Is.True);
Assert.That(releaseRenderer.sprite, Is.SameAs(pausedReleaseFrame));
Assert.That(RunManager.Instance.ClosePause(), Is.True);
yield return WaitForArtifactCompletion(artifacts, visual);
}
[UnityTest]
public IEnumerator ChainLightning_NoTargetDoesNotEmitRelease()
{
yield return LoadCombatPrototype();
SpawnDirector director = Object.FindAnyObjectByType<SpawnDirector>();
if (director != null)
{
director.enabled = false;
}
foreach (EnemyController enemy in Object.FindObjectsByType<EnemyController>(
FindObjectsSortMode.None))
{
enemy.gameObject.SetActive(false);
}
Keyboard keyboard = BeginVirtualKeyboardInput();
PlayerController player = Object.FindAnyObjectByType<PlayerController>();
ActiveArtifactController artifacts =
player.GetComponent<ActiveArtifactController>();
ArtifactChargeVisual visual =
player.GetComponent<ArtifactChargeVisual>();
ActiveArtifactDefinition definition = SelectArtifact(
artifacts,
ActiveArtifactEffect.ChainLightning);
EnsureGauge(artifacts, definition.NormalGaugeCost);
InputSystem.QueueStateEvent(keyboard, new KeyboardState(Key.A));
yield return null;
InputSystem.QueueStateEvent(keyboard, new KeyboardState());
yield return null;
Assert.That(visual.ReleasePlayCount, Is.Zero);
Assert.That(visual.IsAuraVisible, Is.False);
Assert.That(visual.IsReadyAuraVisible, Is.False);
}
[UnityTest]
public IEnumerator ChargeVisual_CancelDamageSelectionAndDisableClearImmediately()
{
yield return LoadCombatPrototype();
DisableCombatActors();
Keyboard keyboard = BeginVirtualKeyboardInput();
PlayerController player = Object.FindAnyObjectByType<PlayerController>();
ActiveArtifactController artifacts =
player.GetComponent<ActiveArtifactController>();
ArtifactChargeVisual visual =
player.GetComponent<ArtifactChargeVisual>();
PlayerHealth health = player.GetComponent<PlayerHealth>();
ActiveArtifactDefinition definition = SelectArtifact(
artifacts,
ActiveArtifactEffect.Dash);
EnsureGauge(artifacts, definition.NormalGaugeCost);
yield return PressA(keyboard);
Assert.That(visual.IsAuraVisible, Is.True);
Assert.That(RunManager.Instance.TryOpenPause(), Is.True);
yield return null;
Assert.That(visual.IsAuraVisible, Is.False);
Assert.That(RunManager.Instance.ClosePause(), Is.True);
yield return ReleaseA(keyboard);
yield return PressA(keyboard);
Assert.That(visual.IsAuraVisible, Is.True);
Assert.That(
health.TryTakeDamage(1f, Vector2.left, 0f),
Is.True);
Assert.That(visual.IsAuraVisible, Is.False);
yield return ReleaseA(keyboard);
yield return new WaitForSeconds(0.6f);
yield return PressA(keyboard);
Assert.That(visual.IsAuraVisible, Is.True);
visual.enabled = false;
Assert.That(visual.IsAuraVisible, Is.False);
visual.enabled = true;
yield return null;
Assert.That(visual.IsAuraVisible, Is.True);
RunManager.Instance.SetSelectionOpen(true);
Assert.That(visual.IsAuraVisible, Is.False);
yield return null;
RunManager.Instance.SetSelectionOpen(false);
yield return null;
Assert.That(visual.IsAuraVisible, Is.False);
yield return ReleaseA(keyboard);
yield return PressA(keyboard);
Assert.That(visual.IsAuraVisible, Is.True);
artifacts.enabled = false;
yield return null;
Assert.That(visual.IsAuraVisible, Is.False);
artifacts.enabled = true;
yield return ReleaseA(keyboard);
SetGauge(artifacts, 0f);
yield return PressA(keyboard);
Assert.That(visual.IsAuraVisible, Is.False);
yield return ReleaseA(keyboard);
// Hurt ends before damage immunity. The death cleanup check needs
// a new accepted hit after the earlier hit's protection expires.
float immunityDeadline = Time.realtimeSinceStartup
+ health.InvulnerabilityDuration + 1f;
while (health.IsInvulnerable
&& Time.realtimeSinceStartup < immunityDeadline)
{
yield return null;
}
Assert.That(health.IsInvulnerable, Is.False);
EnsureGauge(artifacts, definition.NormalGaugeCost);
yield return PressA(keyboard);
Assert.That(visual.IsAuraVisible, Is.True);
Assert.That(
health.TryTakeDamage(health.CurrentHealth + 1f, Vector2.left, 0f),
Is.True);
Assert.That(visual.IsAuraVisible, Is.False);
Assert.That(visual.IsReadyAuraVisible, Is.False);
Assert.That(visual.IsFlashVisible, Is.False);
Assert.That(visual.IsReleaseVisible, Is.False);
}
[UnityTest]
public IEnumerator SixArtifacts_NormalAndChargedUseEmitOneReleaseEach()
{
yield return LoadCombatPrototype();
SpawnDirector director = Object.FindAnyObjectByType<SpawnDirector>();
if (director != null)
{
director.enabled = false;
}
foreach (EnemyController enemy in Object.FindObjectsByType<EnemyController>(
FindObjectsSortMode.None))
{
enemy.gameObject.SetActive(false);
}
PlayerController player = Object.FindAnyObjectByType<PlayerController>();
Rigidbody2D playerBody = player.GetComponent<Rigidbody2D>();
ActiveArtifactController artifacts =
player.GetComponent<ActiveArtifactController>();
ArtifactChargeVisual visual =
player.GetComponent<ArtifactChargeVisual>();
BeginVirtualKeyboardInput();
ActiveArtifactEffect[] effects =
{
ActiveArtifactEffect.Dash,
ActiveArtifactEffect.Pulse,
ActiveArtifactEffect.Phoenix,
ActiveArtifactEffect.Cyclone,
ActiveArtifactEffect.ThunderCrash,
ActiveArtifactEffect.ChainLightning,
};
EnemyController chainTarget = null;
for (int i = 0; i < effects.Length; i++)
{
ActiveArtifactDefinition definition = SelectArtifact(
artifacts,
effects[i]);
EnsureGauge(artifacts, definition.ChargedGaugeCost);
Invoke(artifacts, "BeginCharge");
yield return null;
Assert.That(visual.IsAuraVisible, Is.True);
Assert.That(
visual.AuraObject.GetComponent<SpriteRenderer>().sprite.texture.name,
Does.Contain(effects[i].ToString()));
int flashCountBefore = visual.FlashPlayCount;
SetChargeAsFullyCharged(artifacts, definition);
yield return null;
Assert.That(visual.IsReadyAuraVisible, Is.True);
Assert.That(
visual.FlashPlayCount,
Is.EqualTo(flashCountBefore + 1));
yield return null;
Assert.That(
visual.FlashPlayCount,
Is.EqualTo(flashCountBefore + 1));
Invoke(artifacts, "CancelCharge");
Assert.That(visual.IsReadyAuraVisible, Is.False);
if (effects[i] == ActiveArtifactEffect.ChainLightning)
{
director.DebugSpawnImmediate(1);
yield return null;
chainTarget = Object.FindAnyObjectByType<EnemyController>();
Assert.That(chainTarget, Is.Not.Null);
chainTarget.enabled = false;
typeof(EnemyController)
.GetProperty("CurrentHealth")
?.SetValue(chainTarget, 100000f);
Rigidbody2D targetBody = chainTarget.GetComponent<Rigidbody2D>();
targetBody.position = playerBody.position + Vector2.right;
Physics2D.SyncTransforms();
}
EnsureGauge(artifacts, definition.NormalGaugeCost);
int releaseCountBefore = visual.ReleasePlayCount;
Assert.That(artifacts.TryUseCurrent(false), Is.True);
yield return WaitForArtifactCompletion(artifacts, visual);
Assert.That(
visual.ReleasePlayCount,
Is.EqualTo(releaseCountBefore + 1));
EnsureGauge(artifacts, definition.ChargedGaugeCost);
releaseCountBefore = visual.ReleasePlayCount;
Assert.That(artifacts.TryUseCurrent(true), Is.True);
yield return WaitForArtifactCompletion(artifacts, visual);
Assert.That(
visual.ReleasePlayCount,
Is.EqualTo(releaseCountBefore + 1));
}
if (chainTarget != null)
{
Object.Destroy(chainTarget.gameObject);
}
}
private Keyboard BeginVirtualKeyboardInput()
{
inputSettingsCaptured = true;
previousBackgroundBehavior = InputSystem.settings.backgroundBehavior;
previousEditorInputBehavior =
InputSystem.settings.editorInputBehaviorInPlayMode;
previousRunInBackground = Application.runInBackground;
InputSystem.settings.backgroundBehavior =
InputSettings.BackgroundBehavior.IgnoreFocus;
InputSystem.settings.editorInputBehaviorInPlayMode =
InputSettings.EditorInputBehaviorInPlayMode.AllDeviceInputAlwaysGoesToGameView;
Application.runInBackground = true;
virtualKeyboard = InputSystem.AddDevice<Keyboard>();
virtualKeyboard.MakeCurrent();
virtualKeyboardInputActive = true;
return virtualKeyboard;
}
private static IEnumerator PressA(Keyboard keyboard)
{
InputSystem.QueueStateEvent(keyboard, new KeyboardState(Key.A));
yield return null;
Assert.That(keyboard.aKey.isPressed, Is.True);
}
private static IEnumerator ReleaseA(Keyboard keyboard)
{
InputSystem.QueueStateEvent(keyboard, new KeyboardState());
yield return null;
Assert.That(keyboard.aKey.isPressed, Is.False);
}
private static void DisableCombatActors()
{
SpawnDirector director = Object.FindAnyObjectByType<SpawnDirector>();
if (director != null)
{
director.enabled = false;
}
foreach (EnemyController enemy in Object.FindObjectsByType<EnemyController>(
FindObjectsSortMode.None))
{
enemy.gameObject.SetActive(false);
}
}
private static void SetChargeAsFullyCharged(
ActiveArtifactController artifacts,
ActiveArtifactDefinition definition)
{
typeof(ActiveArtifactController)
.GetField("chargeStartedAt", BindingFlags.Instance | BindingFlags.NonPublic)
?.SetValue(
artifacts,
Time.time - definition.ChargeDuration - 0.01f);
}
private static void Invoke(object target, string method)
{
target.GetType()
.GetMethod(method, BindingFlags.Instance | BindingFlags.NonPublic)
?.Invoke(target, null);
}
private static IEnumerator LoadCombatPrototype()
{
AsyncOperation load = SceneManager.LoadSceneAsync("CombatPrototype");
while (!load.isDone)
{
yield return null;
}
yield return null;
yield return null;
}
private static IEnumerator WaitForArtifactCompletion(
ActiveArtifactController artifacts,
ArtifactChargeVisual visual)
{
DashController dash = artifacts.GetComponent<DashController>();
float scaledDeadline = Time.time + 2.5f;
float realtimeDeadline = Time.realtimeSinceStartup + 3f;
while ((artifacts.IsExecutingArtifact
|| (dash != null && dash.IsDashing)
|| visual.IsReleaseVisible)
&& Time.time < scaledDeadline
&& Time.realtimeSinceStartup < realtimeDeadline)
{
yield return null;
}
Assert.That(artifacts.IsExecutingArtifact, Is.False);
if (dash != null)
{
Assert.That(dash.IsDashing, Is.False);
}
Assert.That(visual.IsReleaseVisible, Is.False);
}
private static ActiveArtifactDefinition SelectArtifact(
ActiveArtifactController artifacts,
ActiveArtifactEffect effect)
{
for (int i = 0; i < artifacts.OwnedArtifactCount; i++)
{
if (artifacts.CurrentArtifact != null
&& artifacts.CurrentArtifact.Effect == effect)
{
return artifacts.CurrentArtifact;
}
Assert.That(artifacts.SelectNext(), Is.True);
}
Assert.Fail($"Artifact {effect} was not found in the debug catalog.");
return null;
}
private static void EnsureGauge(
ActiveArtifactController artifacts,
float required)
{
while (artifacts.CurrentGauge < required)
{
artifacts.AddMovementCharge(1f);
}
}
private static void SetGauge(
ActiveArtifactController artifacts,
float value)
{
typeof(ActiveArtifactController)
.GetField(
"<CurrentGauge>k__BackingField",
BindingFlags.Instance | BindingFlags.NonPublic)
?.SetValue(artifacts, Mathf.Max(0f, value));
}
}
}