356 lines
15 KiB
C#
356 lines
15 KiB
C#
using System.Collections;
|
|
using System.Reflection;
|
|
using BumpCombat.Combat;
|
|
using BumpCombat.Core;
|
|
using BumpCombat.Player;
|
|
using NUnit.Framework;
|
|
using UnityEngine;
|
|
using UnityEngine.InputSystem;
|
|
using UnityEngine.InputSystem.LowLevel;
|
|
using UnityEngine.TestTools;
|
|
|
|
namespace BumpCombat.PlayModeTests
|
|
{
|
|
public sealed class ArtifactSwitchEventPlayModeTests
|
|
{
|
|
private GameObject testPlayer;
|
|
private ActiveArtifactController artifacts;
|
|
private ActiveArtifactDefinition firstArtifact;
|
|
private ActiveArtifactDefinition secondArtifact;
|
|
private Keyboard virtualKeyboard;
|
|
private bool virtualKeyboardInputActive;
|
|
private InputSettings.BackgroundBehavior previousBackgroundBehavior;
|
|
private InputSettings.EditorInputBehaviorInPlayMode previousEditorInputBehavior;
|
|
private bool inputSettingsCaptured;
|
|
|
|
[SetUp]
|
|
public void SetUp()
|
|
{
|
|
Time.timeScale = 1f;
|
|
}
|
|
|
|
[TearDown]
|
|
public void TearDown()
|
|
{
|
|
Time.timeScale = 1f;
|
|
if (virtualKeyboardInputActive
|
|
&& virtualKeyboard != null
|
|
&& virtualKeyboard.added)
|
|
{
|
|
InputSystem.QueueStateEvent(virtualKeyboard, new KeyboardState());
|
|
InputSystem.RemoveDevice(virtualKeyboard);
|
|
}
|
|
|
|
if (inputSettingsCaptured)
|
|
{
|
|
InputSystem.settings.backgroundBehavior = previousBackgroundBehavior;
|
|
InputSystem.settings.editorInputBehaviorInPlayMode =
|
|
previousEditorInputBehavior;
|
|
}
|
|
|
|
if (testPlayer != null)
|
|
{
|
|
Object.Destroy(testPlayer);
|
|
}
|
|
|
|
if (firstArtifact != null)
|
|
{
|
|
Object.Destroy(firstArtifact);
|
|
}
|
|
|
|
if (secondArtifact != null)
|
|
{
|
|
Object.Destroy(secondArtifact);
|
|
}
|
|
|
|
virtualKeyboard = null;
|
|
virtualKeyboardInputActive = false;
|
|
inputSettingsCaptured = false;
|
|
}
|
|
|
|
[UnityTest]
|
|
public IEnumerator ArtifactSwitchedEvent_OnlyFiresOnceAfterSuccessfulSwitch()
|
|
{
|
|
RunManager previousRunManager = RunManager.Instance;
|
|
if (previousRunManager == null)
|
|
{
|
|
previousRunManager = null;
|
|
}
|
|
bool previousRunManagerEnabled = previousRunManager != null
|
|
&& previousRunManager.enabled;
|
|
if (previousRunManager != null)
|
|
{
|
|
previousRunManager.enabled = false;
|
|
}
|
|
|
|
SetRunManagerInstanceForTest(null);
|
|
Time.timeScale = 1f;
|
|
ActiveArtifactController[] existingControllers =
|
|
Object.FindObjectsByType<ActiveArtifactController>(
|
|
FindObjectsSortMode.None);
|
|
bool[] previousControllerStates = new bool[existingControllers.Length];
|
|
for (int i = 0; i < existingControllers.Length; i++)
|
|
{
|
|
previousControllerStates[i] = existingControllers[i].enabled;
|
|
existingControllers[i].enabled = false;
|
|
}
|
|
|
|
Keyboard keyboard = null;
|
|
try
|
|
{
|
|
testPlayer = new GameObject("Artifact Switch Event Test Player");
|
|
testPlayer.AddComponent<PlayerStats>();
|
|
artifacts = testPlayer.AddComponent<ActiveArtifactController>();
|
|
firstArtifact = CreateArtifact(
|
|
"switch-event-dash",
|
|
ActiveArtifactEffect.Dash);
|
|
secondArtifact = CreateArtifact(
|
|
"switch-event-pulse",
|
|
ActiveArtifactEffect.Pulse);
|
|
|
|
int switchCount = 0;
|
|
int chargeStartCount = 0;
|
|
ActiveArtifactDefinition eventDefinition = null;
|
|
ActiveArtifactDefinition selectedDuringEvent = null;
|
|
artifacts.OnArtifactSwitched += definition =>
|
|
{
|
|
switchCount++;
|
|
eventDefinition = definition;
|
|
selectedDuringEvent = artifacts.CurrentArtifact;
|
|
};
|
|
artifacts.OnArtifactChargeStarted += _ => chargeStartCount++;
|
|
int useCount = 0;
|
|
artifacts.OnArtifactUseSucceeded += (_, _) => useCount++;
|
|
|
|
Assert.That(artifacts.TryAddArtifact(firstArtifact), Is.True);
|
|
Assert.That(artifacts.SelectNext(), Is.False);
|
|
Assert.That(switchCount, Is.Zero,
|
|
"Acquisition and a failed switch must not emit the switch event.");
|
|
|
|
artifacts.AddMovementCharge(1f);
|
|
artifacts.AddMovementCharge(49f);
|
|
Assert.That(artifacts.CurrentGauge, Is.GreaterThan(0f));
|
|
Assert.That(artifacts.CurrentGauge, Is.EqualTo(100f));
|
|
Assert.That(switchCount, Is.Zero,
|
|
"Gauge changes must not emit the switch event.");
|
|
|
|
keyboard = BeginVirtualKeyboardInput();
|
|
InputSystem.QueueStateEvent(keyboard, new KeyboardState(Key.A));
|
|
yield return null;
|
|
Assert.That(artifacts.IsCharging, Is.True);
|
|
Assert.That(chargeStartCount, Is.EqualTo(1));
|
|
Assert.That(artifacts.SelectNext(), Is.False);
|
|
Assert.That(switchCount, Is.Zero,
|
|
"A one-artifact inventory cannot switch and keeps charging.");
|
|
Assert.That(artifacts.IsCharging, Is.True,
|
|
"A failed single-slot switch must preserve the charge.");
|
|
|
|
Assert.That(artifacts.TryAddArtifact(secondArtifact), Is.True);
|
|
Assert.That(switchCount, Is.Zero,
|
|
"Adding another owned artifact must not be reported as switching.");
|
|
yield return new WaitForSeconds(firstArtifact.ChargeDuration * 0.5f);
|
|
Assert.That(artifacts.ChargeProgress, Is.GreaterThan(0f).And.LessThan(1f));
|
|
|
|
Assert.That(artifacts.SelectNext(), Is.True);
|
|
Assert.That(artifacts.IsCharging, Is.False,
|
|
"A successful switch must cancel a partial charge.");
|
|
Assert.That(switchCount, Is.EqualTo(1));
|
|
Assert.That(eventDefinition, Is.SameAs(secondArtifact));
|
|
Assert.That(selectedDuringEvent, Is.SameAs(secondArtifact),
|
|
"The event must observe the newly selected artifact.");
|
|
Assert.That(artifacts.CurrentArtifact, Is.SameAs(secondArtifact));
|
|
Assert.That(artifacts.CurrentGauge, Is.EqualTo(100f),
|
|
"Switching during a partial charge must not spend gauge.");
|
|
Assert.That(useCount, Is.Zero,
|
|
"Switching during a partial charge must not activate an artifact.");
|
|
|
|
InputSystem.QueueStateEvent(keyboard, new KeyboardState());
|
|
yield return null;
|
|
Assert.That(useCount, Is.Zero,
|
|
"Releasing the held charge after switching must not activate either artifact.");
|
|
}
|
|
finally
|
|
{
|
|
if (keyboard != null && keyboard.added)
|
|
{
|
|
InputSystem.QueueStateEvent(keyboard, new KeyboardState());
|
|
}
|
|
|
|
for (int i = 0; i < existingControllers.Length; i++)
|
|
{
|
|
if (existingControllers[i] != null)
|
|
{
|
|
existingControllers[i].enabled = previousControllerStates[i];
|
|
}
|
|
}
|
|
|
|
if (previousRunManager != null)
|
|
{
|
|
previousRunManager.enabled = previousRunManagerEnabled;
|
|
}
|
|
SetRunManagerInstanceForTest(previousRunManager);
|
|
}
|
|
}
|
|
|
|
[UnityTest]
|
|
public IEnumerator FullyChargedSwitch_CancelsWithoutActivationOrGaugeCost()
|
|
{
|
|
testPlayer = new GameObject("Fully Charged Artifact Switch Test Player");
|
|
testPlayer.AddComponent<PlayerStats>();
|
|
artifacts = testPlayer.AddComponent<ActiveArtifactController>();
|
|
firstArtifact = CreateArtifact(
|
|
"fully-charged-switch-pulse",
|
|
ActiveArtifactEffect.Pulse);
|
|
secondArtifact = CreateArtifact(
|
|
"fully-charged-switch-cyclone",
|
|
ActiveArtifactEffect.Cyclone);
|
|
Assert.That(artifacts.TryAddArtifact(firstArtifact), Is.True);
|
|
Assert.That(artifacts.TryAddArtifact(secondArtifact), Is.True);
|
|
artifacts.AddMovementCharge(50f);
|
|
float gaugeBeforeSwitch = artifacts.CurrentGauge;
|
|
int useCount = 0;
|
|
artifacts.OnArtifactUseSucceeded += (_, _) => useCount++;
|
|
|
|
InvokePrivate(artifacts, "BeginCharge");
|
|
typeof(ActiveArtifactController)
|
|
.GetField("chargeStartedAt", BindingFlags.Instance | BindingFlags.NonPublic)
|
|
?.SetValue(
|
|
artifacts,
|
|
Time.time - firstArtifact.ChargeDuration - 0.01f);
|
|
Assert.That(artifacts.IsFullyCharged, Is.True);
|
|
|
|
Assert.That(artifacts.SelectNext(), Is.True);
|
|
Assert.That(artifacts.CurrentArtifact, Is.SameAs(secondArtifact));
|
|
Assert.That(artifacts.IsCharging, Is.False);
|
|
Assert.That(artifacts.IsExecutingArtifact, Is.False);
|
|
Assert.That(artifacts.CurrentGauge, Is.EqualTo(gaugeBeforeSwitch));
|
|
Assert.That(useCount, Is.Zero);
|
|
|
|
yield return null;
|
|
}
|
|
|
|
[UnityTest]
|
|
public IEnumerator SWhileHoldingA_CancelsChargeAndReleaseWaitsForFreshPress()
|
|
{
|
|
testPlayer = new GameObject("Held A Artifact Switch Test Player");
|
|
testPlayer.AddComponent<PlayerStats>();
|
|
artifacts = testPlayer.AddComponent<ActiveArtifactController>();
|
|
firstArtifact = CreateArtifact(
|
|
"held-a-switch-pulse",
|
|
ActiveArtifactEffect.Pulse);
|
|
secondArtifact = CreateArtifact(
|
|
"held-a-switch-cyclone",
|
|
ActiveArtifactEffect.Cyclone);
|
|
Assert.That(artifacts.TryAddArtifact(firstArtifact), Is.True);
|
|
Assert.That(artifacts.TryAddArtifact(secondArtifact), Is.True);
|
|
artifacts.AddMovementCharge(50f);
|
|
float gaugeBeforeSwitch = artifacts.CurrentGauge;
|
|
int chargeStartCount = 0;
|
|
int useCount = 0;
|
|
artifacts.OnArtifactChargeStarted += _ => chargeStartCount++;
|
|
artifacts.OnArtifactUseSucceeded += (_, _) => useCount++;
|
|
Keyboard keyboard = BeginVirtualKeyboardInput();
|
|
|
|
InputSystem.QueueStateEvent(keyboard, new KeyboardState(Key.A));
|
|
yield return null;
|
|
Assert.That(artifacts.IsCharging, Is.True);
|
|
Assert.That(chargeStartCount, Is.EqualTo(1));
|
|
yield return new WaitForSeconds(firstArtifact.ChargeDuration + 0.05f);
|
|
Assert.That(artifacts.IsFullyCharged, Is.True);
|
|
|
|
InputSystem.QueueStateEvent(keyboard, new KeyboardState(Key.A, Key.S));
|
|
yield return null;
|
|
Assert.That(keyboard.aKey.isPressed, Is.True);
|
|
Assert.That(keyboard.sKey.isPressed, Is.True);
|
|
Assert.That(artifacts.CurrentArtifact, Is.SameAs(secondArtifact));
|
|
Assert.That(artifacts.IsCharging, Is.False,
|
|
"S must cancel a fully charged artifact while A remains held.");
|
|
Assert.That(artifacts.CurrentGauge, Is.EqualTo(gaugeBeforeSwitch));
|
|
Assert.That(useCount, Is.Zero);
|
|
|
|
InputSystem.QueueStateEvent(keyboard, new KeyboardState());
|
|
yield return null;
|
|
Assert.That(artifacts.IsCharging, Is.False);
|
|
Assert.That(artifacts.IsExecutingArtifact, Is.False);
|
|
Assert.That(artifacts.CurrentGauge, Is.EqualTo(gaugeBeforeSwitch));
|
|
Assert.That(useCount, Is.Zero,
|
|
"Releasing the A held through the switch must not activate either artifact.");
|
|
|
|
InputSystem.QueueStateEvent(keyboard, new KeyboardState(Key.A));
|
|
yield return null;
|
|
Assert.That(artifacts.IsCharging, Is.True,
|
|
"A fresh press after switching may begin a new charge.");
|
|
Assert.That(chargeStartCount, Is.EqualTo(2));
|
|
Assert.That(artifacts.CurrentArtifact, Is.SameAs(secondArtifact));
|
|
|
|
InvokePrivate(artifacts, "CancelCharge");
|
|
InputSystem.QueueStateEvent(keyboard, new KeyboardState());
|
|
yield return null;
|
|
Assert.That(useCount, Is.Zero);
|
|
Assert.That(artifacts.CurrentGauge, Is.EqualTo(gaugeBeforeSwitch));
|
|
}
|
|
|
|
private static ActiveArtifactDefinition CreateArtifact(
|
|
string id,
|
|
ActiveArtifactEffect effect)
|
|
{
|
|
ActiveArtifactDefinition definition =
|
|
ScriptableObject.CreateInstance<ActiveArtifactDefinition>();
|
|
definition.Configure(
|
|
id,
|
|
id,
|
|
"",
|
|
effect,
|
|
DamageTag.Collision,
|
|
Color.white,
|
|
Color.white,
|
|
25f,
|
|
60f,
|
|
0.8f,
|
|
15f,
|
|
30f,
|
|
1.25f,
|
|
2f,
|
|
1.5f,
|
|
3f);
|
|
return definition;
|
|
}
|
|
|
|
private Keyboard BeginVirtualKeyboardInput()
|
|
{
|
|
previousBackgroundBehavior = InputSystem.settings.backgroundBehavior;
|
|
previousEditorInputBehavior =
|
|
InputSystem.settings.editorInputBehaviorInPlayMode;
|
|
inputSettingsCaptured = true;
|
|
InputSystem.settings.backgroundBehavior =
|
|
InputSettings.BackgroundBehavior.IgnoreFocus;
|
|
InputSystem.settings.editorInputBehaviorInPlayMode =
|
|
InputSettings.EditorInputBehaviorInPlayMode.AllDeviceInputAlwaysGoesToGameView;
|
|
virtualKeyboard = InputSystem.AddDevice<Keyboard>();
|
|
virtualKeyboard.MakeCurrent();
|
|
virtualKeyboardInputActive = true;
|
|
return virtualKeyboard;
|
|
}
|
|
|
|
private static void InvokePrivate(
|
|
ActiveArtifactController target,
|
|
string methodName)
|
|
{
|
|
MethodInfo method = typeof(ActiveArtifactController).GetMethod(
|
|
methodName,
|
|
BindingFlags.Instance | BindingFlags.NonPublic);
|
|
Assert.That(method, Is.Not.Null, $"Could not find {methodName}.");
|
|
method.Invoke(target, null);
|
|
}
|
|
|
|
private static void SetRunManagerInstanceForTest(RunManager instance)
|
|
{
|
|
FieldInfo instanceField = typeof(RunManager).GetField(
|
|
"<Instance>k__BackingField",
|
|
BindingFlags.Static | BindingFlags.NonPublic);
|
|
Assert.That(instanceField, Is.Not.Null);
|
|
instanceField.SetValue(null, instance);
|
|
}
|
|
}
|
|
}
|