998 lines
50 KiB
C#
998 lines
50 KiB
C#
using System.Collections;
|
|
using System.Collections.Generic;
|
|
using System;
|
|
using System.Reflection;
|
|
using BumpCombat.Core;
|
|
using BumpCombat.Defense;
|
|
using BumpCombat.Constants;
|
|
using BumpCombat.Defense.Comparison;
|
|
using BumpCombat.Enemies;
|
|
using BumpCombat.Progression;
|
|
using NUnit.Framework;
|
|
using UnityEditor;
|
|
using UnityEngine;
|
|
using UnityEngine.InputSystem;
|
|
using UnityEngine.InputSystem.LowLevel;
|
|
using UnityEngine.TestTools;
|
|
using UnityEngine.UI;
|
|
using Object = UnityEngine.Object;
|
|
|
|
namespace BumpCombat.Tests
|
|
{
|
|
public sealed class Defense25DRunHudPlayModeTests
|
|
{
|
|
private const string ConstantsPath =
|
|
"Assets/_Project/Constants/Defense25DComparison/RunHudConstants.asset";
|
|
private GameObject hudTestWorldObject;
|
|
private GameObject hudTestObject;
|
|
private Defense25DComparisonSettings hudTestSettings;
|
|
private Defense25DRunDefinition hudTestRunDefinition;
|
|
private Defense25DWaveDirectorConstants hudTestWaveConstants;
|
|
private Defense25DProgression hudTestProgression;
|
|
private Defense25DRewardConstants hudTestRewardConstants;
|
|
private ModifierDefinition hudTestModifier;
|
|
private float previousTimeScale;
|
|
private InputSettings.BackgroundBehavior previousBackgroundBehavior;
|
|
private InputSettings.EditorInputBehaviorInPlayMode previousEditorInputBehavior;
|
|
private bool previousRunInBackground;
|
|
private bool hudInputSettingsCaptured;
|
|
|
|
[SetUp]
|
|
public void CaptureTimeScale()
|
|
{
|
|
previousTimeScale = Time.timeScale;
|
|
}
|
|
|
|
[Test]
|
|
public void RunHudConstantsAssetMatchesTheFhdLayoutContract()
|
|
{
|
|
Defense25DRunHudConstants constants =
|
|
AssetDatabase.LoadAssetAtPath<Defense25DRunHudConstants>(ConstantsPath);
|
|
|
|
Assert.That(constants, Is.Not.Null);
|
|
Assert.That(constants.CombatHudBottomLeft, Is.EqualTo(new Vector2(24f, 24f)));
|
|
Assert.That(constants.CombatHudSize, Is.EqualTo(new Vector2(392f, 136f)));
|
|
Assert.That(constants.HealthTrackSize, Is.EqualTo(new Vector2(352f, 12f)));
|
|
Assert.That(constants.ArtifactGaugeSize, Is.EqualTo(new Vector2(352f, 18f)));
|
|
Assert.That(constants.ExperienceTrackSize, Is.EqualTo(new Vector2(352f, 8f)));
|
|
Assert.That(constants.GuardTrackSize, Is.EqualTo(new Vector2(18f, 94f)));
|
|
Assert.That(constants.TimerSize, Is.EqualTo(new Vector2(220f, 66f)));
|
|
Assert.That(constants.WaveSummarySize, Is.EqualTo(new Vector2(700f, 116f)));
|
|
Assert.That(constants.MinimapSize, Is.EqualTo(new Vector2(300f, 162f)));
|
|
Assert.That(constants.PortalListSize, Is.EqualTo(new Vector2(320f, 300f)));
|
|
Assert.That(constants.PortalCount, Is.EqualTo(12));
|
|
Assert.That(constants.MissionCount, Is.EqualTo(3));
|
|
Assert.That(constants.ModalMaximumSize, Is.EqualTo(new Vector2(1040f, 420f)));
|
|
}
|
|
|
|
[TestCase(1280f, 720f, 2f / 3f)]
|
|
[TestCase(1920f, 1080f, 1f)]
|
|
[TestCase(2560f, 1440f, 4f / 3f)]
|
|
[TestCase(1399f, 799f, 1399f / 1920f)]
|
|
public void ViewportScaleUsesTheSmallerReferenceAxis(
|
|
float width, float height, float expectedScale)
|
|
{
|
|
float scale = Defense25DRunHudConstants.ScaleForViewport(
|
|
new Rect(0f, 0f, width, height));
|
|
|
|
Assert.That(scale, Is.EqualTo(expectedScale).Within(.0001f));
|
|
}
|
|
|
|
[TestCase(0f, 1080f)]
|
|
[TestCase(1920f, 0f)]
|
|
[TestCase(-1f, 720f)]
|
|
public void InvalidViewportHasNoLayoutScale(float width, float height)
|
|
{
|
|
Assert.That(Defense25DRunHudConstants.IsValidViewport(
|
|
new Rect(0f, 0f, width, height)), Is.False);
|
|
Assert.That(Defense25DRunHudConstants.ScaleForViewport(
|
|
new Rect(0f, 0f, width, height)), Is.Zero);
|
|
}
|
|
|
|
[UnityTest]
|
|
public IEnumerator BindCreatesRealButtonHitRectsAndUnbindClearsThem()
|
|
{
|
|
GameObject host = new("Run HUD PlayMode Test");
|
|
Defense25DRunHud hud = host.AddComponent<Defense25DRunHud>();
|
|
hud.Bind(null, null, null);
|
|
yield return null;
|
|
|
|
Assert.That(hud.IsLayoutValid, Is.True);
|
|
Rect[] firstRects = hud.GetPointerBlockRects();
|
|
Assert.That(firstRects, Is.Not.Empty);
|
|
foreach (Rect hitRect in firstRects)
|
|
Assert.That(hitRect.width * hitRect.height, Is.GreaterThan(0f));
|
|
|
|
Graphic[] graphics = host.GetComponentsInChildren<Graphic>(true);
|
|
foreach (Graphic graphic in graphics)
|
|
if (graphic.raycastTarget)
|
|
Assert.That(graphic.GetComponent<Button>(), Is.Not.Null,
|
|
$"Decorative graphic '{graphic.name}' must not intercept UI raycasts.");
|
|
|
|
hud.Bind(null, null, null);
|
|
yield return null;
|
|
Assert.That(hud.GetPointerBlockRects(), Has.Length.EqualTo(firstRects.Length));
|
|
|
|
hud.Unbind();
|
|
Assert.That(hud.IsLayoutValid, Is.False);
|
|
Assert.That(hud.GetPointerBlockRects(), Is.Empty);
|
|
Object.Destroy(host);
|
|
yield return null;
|
|
}
|
|
|
|
[UnityTest]
|
|
public IEnumerator InvalidViewportHidesHudAndValidViewportRestoresIt()
|
|
{
|
|
GameObject host = new("Run HUD Viewport Test");
|
|
Defense25DRunHud hud = host.AddComponent<Defense25DRunHud>();
|
|
hud.Bind(null, null, null);
|
|
|
|
hud.RefreshLayout(new Rect(0f, 0f, 0f, 1080f));
|
|
Assert.That(hud.IsLayoutValid, Is.False);
|
|
Assert.That(hud.GetPointerBlockRects(), Is.Empty);
|
|
|
|
hud.RefreshLayout(new Rect(0f, 0f, 1920f, 1080f));
|
|
yield return null;
|
|
Assert.That(hud.IsLayoutValid, Is.True);
|
|
Assert.That(hud.GetPointerBlockRects(), Is.Not.Empty);
|
|
|
|
hud.Unbind();
|
|
Object.Destroy(host);
|
|
yield return null;
|
|
}
|
|
|
|
[TestCase(1280, 720, 0f, 0f, 1280f, 720f)]
|
|
[TestCase(1920, 1080, 0f, 0f, 1920f, 1080f)]
|
|
[TestCase(2560, 1440, 0f, 0f, 2560f, 1440f)]
|
|
[TestCase(1399, 799, 59.5f, 39.5f, 1280f, 720f)]
|
|
public void ViewportKeepsWorldRenderScaleAndLetterboxBounds(
|
|
int screenWidth, int screenHeight,
|
|
float x, float y, float width, float height)
|
|
{
|
|
Rect viewport = Defense25DView.GetViewport(screenWidth, screenHeight);
|
|
|
|
Assert.That(viewport.x, Is.EqualTo(x).Within(.001f));
|
|
Assert.That(viewport.y, Is.EqualTo(y).Within(.001f));
|
|
Assert.That(viewport.width, Is.EqualTo(width).Within(.001f));
|
|
Assert.That(viewport.height, Is.EqualTo(height).Within(.001f));
|
|
}
|
|
|
|
[TestCase(1280, 720, 2f / 3f)]
|
|
[TestCase(1920, 1080, 1f)]
|
|
[TestCase(2560, 1440, 4f / 3f)]
|
|
[TestCase(1399, 799, 2f / 3f)]
|
|
public void HudLayoutUsesViewportScaleAtFhdQhdAndLetterboxedSizes(
|
|
int screenWidth, int screenHeight, float expectedScale)
|
|
{
|
|
GameObject host = new("Run HUD viewport layout test");
|
|
try
|
|
{
|
|
Defense25DRunHud hud = host.AddComponent<Defense25DRunHud>();
|
|
hud.Bind(null, null, null);
|
|
Rect viewport = Defense25DView.GetViewport(screenWidth, screenHeight);
|
|
hud.RefreshLayout(viewport);
|
|
|
|
CanvasScaler scaler = hud.HudCanvas.GetComponent<CanvasScaler>();
|
|
Assert.That(scaler.scaleFactor, Is.EqualTo(expectedScale).Within(.001f));
|
|
Vector2 expectedSize = viewport.size / expectedScale;
|
|
Assert.That(hud.ViewportRoot.sizeDelta.x, Is.EqualTo(expectedSize.x).Within(.001f));
|
|
Assert.That(hud.ViewportRoot.sizeDelta.y, Is.EqualTo(expectedSize.y).Within(.001f));
|
|
Assert.That(hud.GetPointerBlockRects(), Is.Not.Empty);
|
|
hud.Unbind();
|
|
}
|
|
finally
|
|
{
|
|
Object.DestroyImmediate(host);
|
|
}
|
|
}
|
|
|
|
[Test]
|
|
public void ViewportGroundFootprintUsesCameraRaysWithoutRenderTexture()
|
|
{
|
|
GameObject cameraObject = new("Viewport Footprint Math Camera");
|
|
try
|
|
{
|
|
Camera camera = cameraObject.AddComponent<Camera>();
|
|
camera.orthographic = true;
|
|
camera.orthographicSize = 6f;
|
|
camera.transform.SetPositionAndRotation(new Vector3(0f, 30f, -20f),
|
|
Quaternion.LookRotation(new Vector3(0f, -.8f, .6f), Vector3.up));
|
|
Assert.That(camera.targetTexture, Is.Null);
|
|
|
|
Assert.That(Defense25DView.TryGetViewportGroundFootprint(camera, 0f,
|
|
Vector2.zero, out Vector2 minimum, out Vector2 maximum), Is.True);
|
|
Assert.That(minimum.x, Is.LessThan(maximum.x));
|
|
Assert.That(minimum.y, Is.LessThan(maximum.y));
|
|
Assert.That(float.IsInfinity(minimum.x) || float.IsNaN(minimum.x), Is.False);
|
|
}
|
|
finally
|
|
{
|
|
Object.DestroyImmediate(cameraObject);
|
|
}
|
|
}
|
|
|
|
[Test]
|
|
public void FollowCenterClampsTheTargetToTheGroundViewportFootprint()
|
|
{
|
|
Vector2 center = Defense25DView.ClampViewportCenter(
|
|
new Vector2(100f, -100f), Vector2.zero, new Vector2(20f, 20f),
|
|
new Vector2(-5f, -8f), new Vector2(7f, 10f));
|
|
|
|
Assert.That(center, Is.EqualTo(new Vector2(13f, -12f)));
|
|
}
|
|
|
|
[Test]
|
|
public void SmallArenaCentersViewportWhenFootprintIsLargerThanBounds()
|
|
{
|
|
Vector2 center = Defense25DView.ClampViewportCenter(
|
|
new Vector2(80f, -60f), new Vector2(3f, -4f), new Vector2(2f, 1f),
|
|
new Vector2(-6f, -5f), new Vector2(8f, 7f));
|
|
|
|
Assert.That(center, Is.EqualTo(new Vector2(2f, -5f)));
|
|
}
|
|
|
|
[UnityTest]
|
|
public IEnumerator TierThreeUpgradeRequiresAnExplicitBranchButtonChoice()
|
|
{
|
|
yield return InitializeHudTestWorld(longRun: true);
|
|
Defense25DActor player = hudTestWorldObject.GetComponent<Defense25DComparison>().Player;
|
|
Defense25DComparison world = hudTestWorldObject.GetComponent<Defense25DComparison>();
|
|
Vector2 firstPad = world.TowerManager.GetPadPosition(0);
|
|
Vector2 secondPad = world.TowerManager.GetPadPosition(1);
|
|
player.ResetAt(firstPad);
|
|
world.TowerManager.AddCurrency(5000);
|
|
Assert.That(world.TowerManager.TryBuildTowerAtPad(0, DefenseTowerKind.Direct), Is.True);
|
|
Assert.That(world.SelectBuildPad(0), Is.True);
|
|
Assert.That(world.UpgradeSelectedTower(DefenseTowerBranch.None), Is.True);
|
|
Assert.That(world.UpgradeSelectedTower(DefenseTowerBranch.None), Is.True);
|
|
hudTestHud.RefreshState();
|
|
|
|
Defense25DTower firstTower = world.TowerManager.GetTowerAtPad(0);
|
|
Assert.That(firstTower.Tier, Is.EqualTo(3));
|
|
FindButton("Upgrade Selected Tower").onClick.Invoke();
|
|
Assert.That(firstTower.Tier, Is.EqualTo(3),
|
|
"An unselected T3 branch must not silently upgrade into First.");
|
|
|
|
Button first = FindButton("Upgrade Branch First");
|
|
Button second = FindButton("Upgrade Branch Second");
|
|
Assert.That(first, Is.Not.Null);
|
|
Assert.That(second, Is.Not.Null);
|
|
Assert.That(GetButtonLabel(first), Does.Contain(
|
|
DefenseTower.GetBranchName(firstTower.Kind, DefenseTowerBranch.First)));
|
|
Assert.That(GetButtonLabel(first), Does.Not.Contain("First"));
|
|
Assert.That(GetButtonLabel(second), Does.Contain(
|
|
DefenseTower.GetBranchName(firstTower.Kind, DefenseTowerBranch.Second)));
|
|
Assert.That(GetButtonLabel(second), Does.Not.Contain("Second"));
|
|
first.onClick.Invoke();
|
|
Assert.That(hudTestHud.SelectedUpgradeBranch, Is.EqualTo(DefenseTowerBranch.First));
|
|
|
|
player.ResetAt(secondPad);
|
|
Assert.That(world.SelectBuildPad(1), Is.True);
|
|
Assert.That(world.TowerManager.TryBuildTowerAtPad(1, DefenseTowerKind.Direct), Is.True);
|
|
Assert.That(world.UpgradeSelectedTower(DefenseTowerBranch.None), Is.True);
|
|
Assert.That(world.UpgradeSelectedTower(DefenseTowerBranch.None), Is.True);
|
|
hudTestHud.RefreshState();
|
|
Defense25DTower secondTower = world.TowerManager.GetTowerAtPad(1);
|
|
Assert.That(hudTestHud.SelectedUpgradeBranch, Is.EqualTo(DefenseTowerBranch.None),
|
|
"A branch chosen for another pad must be discarded when selection changes.");
|
|
FindButton("Upgrade Selected Tower").onClick.Invoke();
|
|
Assert.That(secondTower.Tier, Is.EqualTo(3),
|
|
"The newly selected T3 tower must wait for its own branch choice.");
|
|
FindButton("Upgrade Branch Second").onClick.Invoke();
|
|
FindButton("Upgrade Selected Tower").onClick.Invoke();
|
|
Assert.That(secondTower.Tier, Is.EqualTo(4));
|
|
Assert.That(secondTower.Branch, Is.EqualTo(DefenseTowerBranch.Second));
|
|
|
|
player.ResetAt(firstPad);
|
|
Assert.That(world.SelectBuildPad(0), Is.True);
|
|
hudTestHud.RefreshState();
|
|
FindButton("Upgrade Branch First").onClick.Invoke();
|
|
FindButton("Upgrade Selected Tower").onClick.Invoke();
|
|
Assert.That(firstTower.Tier, Is.EqualTo(4));
|
|
Assert.That(firstTower.Branch, Is.EqualTo(DefenseTowerBranch.First));
|
|
yield return null;
|
|
}
|
|
|
|
[UnityTest]
|
|
public IEnumerator TierThreeBranchKeysUseTowerNamesWithoutUpgradingImmediately()
|
|
{
|
|
yield return InitializeHudTestWorld(longRun: true);
|
|
Defense25DComparison world = hudTestWorldObject.GetComponent<Defense25DComparison>();
|
|
world.AdvanceRunClock(world.PreparationRemaining + .1f);
|
|
Defense25DActor player = world.Player;
|
|
player.ResetAt(world.TowerManager.GetPadPosition(0));
|
|
world.TowerManager.AddCurrency(5000);
|
|
Assert.That(world.TowerManager.TryBuildTowerAtPad(0, DefenseTowerKind.Direct), Is.True);
|
|
Assert.That(world.SelectBuildPad(0), Is.True);
|
|
Assert.That(world.UpgradeSelectedTower(DefenseTowerBranch.None), Is.True);
|
|
Assert.That(world.UpgradeSelectedTower(DefenseTowerBranch.None), Is.True);
|
|
hudTestHud.RefreshState();
|
|
Defense25DTower tower = world.TowerManager.GetTowerAtPad(0);
|
|
Button first = FindButton("Upgrade Branch First");
|
|
Button second = FindButton("Upgrade Branch Second");
|
|
Assert.That(GetButtonLabel(first), Does.Contain(
|
|
DefenseTower.GetBranchName(tower.Kind, DefenseTowerBranch.First)));
|
|
Assert.That(GetButtonLabel(second), Does.Contain(
|
|
DefenseTower.GetBranchName(tower.Kind, DefenseTowerBranch.Second)));
|
|
Assert.That(GetButtonLabel(first), Does.Not.Contain("First"));
|
|
Assert.That(GetButtonLabel(second), Does.Not.Contain("Second"));
|
|
|
|
BeginHudKeyboardInput();
|
|
yield return PressHudKey(Key.J);
|
|
Assert.That(hudTestHud.SelectedUpgradeBranch, Is.EqualTo(DefenseTowerBranch.First));
|
|
Assert.That(tower.Tier, Is.EqualTo(3), "J selects a branch; it does not upgrade.");
|
|
yield return ReleaseHudKeys();
|
|
yield return PressHudKey(Key.K);
|
|
Assert.That(hudTestHud.SelectedUpgradeBranch, Is.EqualTo(DefenseTowerBranch.Second));
|
|
Assert.That(tower.Tier, Is.EqualTo(3), "K selects a branch; it does not upgrade.");
|
|
FindButton("Upgrade Selected Tower").onClick.Invoke();
|
|
Assert.That(tower.Tier, Is.EqualTo(4));
|
|
Assert.That(tower.Branch, Is.EqualTo(DefenseTowerBranch.Second));
|
|
yield return ReleaseHudKeys();
|
|
}
|
|
|
|
[UnityTest]
|
|
public IEnumerator FieldRelicModalUsesTheExistingReadableRelicLabel()
|
|
{
|
|
yield return InitializeHudTestWorld(longRun: true);
|
|
Defense25DComparison world = hudTestWorldObject.GetComponent<Defense25DComparison>();
|
|
FieldInfo choicesField = typeof(Defense25DComparison).GetField(
|
|
"earnedRelicChoices", BindingFlags.Instance | BindingFlags.NonPublic);
|
|
Assert.That(choicesField, Is.Not.Null);
|
|
var choices = choicesField.GetValue(world) as Queue<PassiveRelicId[]>;
|
|
Assert.That(choices, Is.Not.Null);
|
|
choices.Enqueue(new[] { PassiveRelicId.TwinHornTalisman });
|
|
hudTestHud.RefreshState();
|
|
|
|
string label = GetButtonLabel(FindButton("Choice 1"));
|
|
Assert.That(label, Does.Contain("TWIN HORN TALISMAN"));
|
|
Assert.That(label, Does.Contain("ordinary bump deals 2x damage"));
|
|
Assert.That(label, Does.Not.Contain(nameof(PassiveRelicId.TwinHornTalisman)));
|
|
yield return null;
|
|
}
|
|
|
|
[UnityTest]
|
|
public IEnumerator HudPauseKeysRespectPendingChoiceAndRNeverRestartsFromHud()
|
|
{
|
|
yield return InitializeHudTestWorld(longRun: true);
|
|
Defense25DComparison world = hudTestWorldObject.GetComponent<Defense25DComparison>();
|
|
world.AdvanceRunClock(world.PreparationRemaining + .1f);
|
|
world.ReadInput = true;
|
|
BeginHudKeyboardInput();
|
|
int generation = world.RunGeneration;
|
|
|
|
yield return PressHudKey(Key.R);
|
|
Assert.That(world.RunGeneration, Is.EqualTo(generation),
|
|
"The HUD must not bind R to an immediate run reset.");
|
|
yield return ReleaseHudKeys();
|
|
|
|
yield return PressHudKey(Key.P);
|
|
Assert.That(world.Paused, Is.True);
|
|
yield return ReleaseHudKeys();
|
|
yield return PressHudKey(Key.Escape);
|
|
Assert.That(world.Paused, Is.False);
|
|
yield return ReleaseHudKeys();
|
|
|
|
hudTestRewardConstants = Object.Instantiate(
|
|
AssetDatabase.LoadAssetAtPath<Defense25DRewardConstants>(
|
|
"Assets/_Project/Constants/Defense25DComparison/RewardConstants.asset"));
|
|
hudTestModifier = ScriptableObject.CreateInstance<ModifierDefinition>();
|
|
hudTestModifier.Configure("hud.modal-priority", "Modal Priority",
|
|
CharacterStat.MoveSpeed, ModifierOperation.Increased, .1f, 1);
|
|
hudTestProgression = new Defense25DProgression();
|
|
hudTestProgression.Bind(world.Player, hudTestRewardConstants, new[] { hudTestModifier },
|
|
new Defense25DTowerModifiers(hudTestRewardConstants), _ => false);
|
|
Assert.That(hudTestProgression.QueueReward(Defense25DRewardSource.LevelUp,
|
|
"hud-modal-priority"), Is.True);
|
|
hudTestHud.Bind(world, hudTestProgression, null);
|
|
Assert.That(FindButton("Choice 1").gameObject.activeInHierarchy, Is.True);
|
|
|
|
yield return PressHudKey(Key.P);
|
|
Assert.That(world.Paused, Is.False, "A pending choice takes priority over pause input.");
|
|
Assert.That(FindButton("Choice 1").gameObject.activeInHierarchy, Is.True);
|
|
yield return ReleaseHudKeys();
|
|
yield return PressHudKey(Key.Escape);
|
|
Assert.That(world.Paused, Is.False, "Escape must not steal input from a choice modal.");
|
|
Assert.That(FindButton("Choice 1").gameObject.activeInHierarchy, Is.True);
|
|
yield return ReleaseHudKeys();
|
|
|
|
yield return PressHudKey(Key.A);
|
|
Assert.That(FindButton("Choice 1").gameObject.activeInHierarchy, Is.True,
|
|
"Holding A must not resolve or leak through the open choice modal.");
|
|
Assert.That(hudTestProgression.CurrentChoices, Is.Not.Empty);
|
|
Assert.That(GetButtonLabel(FindButton("Choice 1")), Does.Contain("선택하면 즉시 적용"));
|
|
Assert.That(GetButtonLabel(FindButton("Choice 1")), Does.Contain(
|
|
hudTestProgression.CurrentChoices[0].DisplayData.Title));
|
|
Assert.That(GetButtonLabel(FindButton("Choice 1")), Does.Contain(
|
|
hudTestProgression.CurrentChoices[0].DisplayData.Description));
|
|
Assert.That(GetButtonLabel(FindButton("Choice 1")), Does.Contain("등급"));
|
|
string rarityLabel = hudTestProgression.CurrentChoices[0].Rarity switch
|
|
{
|
|
Defense25DRewardRarity.Common => "일반",
|
|
Defense25DRewardRarity.Rare => "희귀",
|
|
Defense25DRewardRarity.Epic => "에픽",
|
|
_ => "등급 없음",
|
|
};
|
|
Assert.That(GetButtonLabel(FindButton("Choice 1")),
|
|
Does.Contain($"등급 · {rarityLabel}"));
|
|
Assert.That(CountOccurrences(GetButtonLabel(FindButton("Choice 1")),
|
|
"선택하면 즉시 적용됩니다"), Is.EqualTo(1),
|
|
"A reward description and the HUD must not duplicate the confirmation copy.");
|
|
yield return ReleaseHudKeys();
|
|
Assert.That(FindButton("Choice 1").gameObject.activeInHierarchy, Is.True,
|
|
"Releasing A after a modal is open must not activate a choice.");
|
|
Assert.That(hudTestHud.IsGameplayInputBlocked, Is.True,
|
|
"The pending choice continues to own gameplay input after A is released.");
|
|
FindButton("Choice 1").onClick.Invoke();
|
|
Assert.That(hudTestProgression.HasPendingLevelChoice, Is.False,
|
|
"Choosing the reward must synchronously resolve its pending level choice.");
|
|
Assert.That(FindButton("Choice 1").gameObject.activeInHierarchy, Is.False,
|
|
"The HUD choice modal must refresh when Progression raises Changed.");
|
|
Assert.That(hudTestHud.IsGameplayInputBlocked, Is.False,
|
|
"Gameplay input is released after the pending choice resolves.");
|
|
}
|
|
|
|
[UnityTest]
|
|
public IEnumerator ResultPanelShowsLedgerProgressionAndMainRunSeedAndEndReason()
|
|
{
|
|
yield return InitializeHudTestWorld(longRun: true);
|
|
Defense25DComparison world = hudTestWorldObject.GetComponent<Defense25DComparison>();
|
|
world.AdvanceRunClock(world.PreparationRemaining + 0.1f);
|
|
|
|
hudTestRewardConstants = Object.Instantiate(
|
|
AssetDatabase.LoadAssetAtPath<Defense25DRewardConstants>(
|
|
"Assets/_Project/Constants/Defense25DComparison/RewardConstants.asset"));
|
|
ModifierDefinition catalogModifier = FindFirstLevelUpModifier();
|
|
bool hasCatalogModifier = catalogModifier != null;
|
|
if (hasCatalogModifier)
|
|
hudTestModifier = Object.Instantiate(catalogModifier);
|
|
else
|
|
{
|
|
hudTestModifier = ScriptableObject.CreateInstance<ModifierDefinition>();
|
|
hudTestModifier.Configure("hud.result", "Result modifier",
|
|
CharacterStat.MoveSpeed, ModifierOperation.Increased, .1f, 1);
|
|
}
|
|
hudTestProgression = new Defense25DProgression();
|
|
hudTestProgression.Bind(world.Player, hudTestRewardConstants, new[] { hudTestModifier },
|
|
new Defense25DTowerModifiers(hudTestRewardConstants), _ => false);
|
|
hudTestHud.Bind(world, hudTestProgression, null);
|
|
Assert.That(hudTestModifier.TryApplyToModel(world.Player, null), Is.True);
|
|
SetPrivateResult(world, "RunFailed", true);
|
|
typeof(Defense25DComparison).GetField("runEndReason",
|
|
BindingFlags.Instance | BindingFlags.NonPublic).SetValue(world,
|
|
Defense25DRunEndReason.FinalBossArrived);
|
|
hudTestHud.RefreshState();
|
|
|
|
string details = FindText("Run Result Details").text;
|
|
Assert.That(FindButton("Restart Run").gameObject.activeInHierarchy, Is.True);
|
|
Assert.That(details, Does.Contain("최종 보스·종료 사유: 최종 보스 도착"));
|
|
Assert.That(details, Does.Contain($"공급 {world.Ledger.SuppliedCount}"));
|
|
Assert.That(details, Does.Contain($"처치 {world.Ledger.KillEventCount}"));
|
|
Assert.That(details, Does.Contain($"시드 {world.CurrentRunSeed}"));
|
|
Assert.That(details, Does.Contain("미션 성공 0 · 실패 0"));
|
|
Assert.That(details, Does.Contain("최종 레벨 1"));
|
|
Assert.That(details, Does.Contain("현재 플레이어 효과:"));
|
|
if (hasCatalogModifier)
|
|
Assert.That(details, Does.Contain(hudTestModifier.GetKoreanDisplayName()));
|
|
else
|
|
Assert.That(details, Does.Contain("기타 활성 효과 1종"));
|
|
Assert.That(details, Does.Not.Contain(hudTestModifier.ModifierId));
|
|
Assert.That(details, Does.Not.Contain(nameof(Defense25DSpawnSource.FinalBoss)));
|
|
|
|
typeof(Defense25DComparison).GetField("runEndReason",
|
|
BindingFlags.Instance | BindingFlags.NonPublic).SetValue(world,
|
|
Defense25DRunEndReason.None);
|
|
SetPrivateResult(world, "RunCleared", true);
|
|
hudTestHud.RefreshState();
|
|
Assert.That(FindText("Run Result Details").text,
|
|
Does.Contain("최종 보스·종료 사유: 최종 보스 처치"));
|
|
|
|
SetPrivateResult(world, "RunFailed", true);
|
|
typeof(Defense25DComparison).GetField("runEndReason",
|
|
BindingFlags.Instance | BindingFlags.NonPublic).SetValue(world,
|
|
Defense25DRunEndReason.BaseDestroyed);
|
|
hudTestHud.RefreshState();
|
|
Assert.That(FindText("Run Result Details").text,
|
|
Does.Contain("최종 보스·종료 사유: 거점 파괴"));
|
|
yield return null;
|
|
}
|
|
|
|
[UnityTest]
|
|
public IEnumerator ActivePausedRestartCanCancelAndConfirmOnlyOnce()
|
|
{
|
|
yield return InitializeHudTestWorld(longRun: false);
|
|
Defense25DComparison world = hudTestWorldObject.GetComponent<Defense25DComparison>();
|
|
world.SetPaused(true);
|
|
hudTestHud.RefreshState();
|
|
int generation = world.RunGeneration;
|
|
Button restart = FindButton("Restart Run");
|
|
|
|
restart.onClick.Invoke();
|
|
restart.onClick.Invoke();
|
|
Assert.That(IsRestartConfirmationVisible(), Is.True);
|
|
Assert.That(world.RunGeneration, Is.EqualTo(generation));
|
|
FindButton("Cancel Restart").onClick.Invoke();
|
|
Assert.That(IsRestartConfirmationVisible(), Is.False);
|
|
Assert.That(world.RunGeneration, Is.EqualTo(generation));
|
|
|
|
restart.onClick.Invoke();
|
|
Button confirm = FindButton("Confirm Restart");
|
|
confirm.onClick.Invoke();
|
|
confirm.onClick.Invoke();
|
|
Assert.That(world.RunGeneration, Is.EqualTo(generation + 1));
|
|
Assert.That(IsRestartConfirmationVisible(), Is.False);
|
|
yield return null;
|
|
}
|
|
|
|
[UnityTest]
|
|
public IEnumerator PendingRewardBlocksRestartUntilChoiceIsResolved()
|
|
{
|
|
yield return InitializeHudTestWorld(longRun: false);
|
|
Defense25DComparison world = hudTestWorldObject.GetComponent<Defense25DComparison>();
|
|
hudTestRewardConstants = ScriptableObject.CreateInstance<Defense25DRewardConstants>();
|
|
hudTestRewardConstants.TowerModifierCatalog = Array.Empty<Defense25DTowerModifierDefinition>();
|
|
hudTestModifier = ScriptableObject.CreateInstance<ModifierDefinition>();
|
|
hudTestModifier.Configure("hud.pending", "hud.pending", CharacterStat.MoveSpeed,
|
|
ModifierOperation.Increased, .1f, 1);
|
|
hudTestProgression = new Defense25DProgression();
|
|
hudTestProgression.Bind(world.Player, hudTestRewardConstants, new[] { hudTestModifier },
|
|
new Defense25DTowerModifiers(hudTestRewardConstants), _ => false);
|
|
Assert.That(hudTestProgression.QueueReward(Defense25DRewardSource.LevelUp,
|
|
"hud-pending-restart"), Is.True);
|
|
hudTestHud.Bind(world, hudTestProgression, null);
|
|
world.SetPaused(true);
|
|
int generation = world.RunGeneration;
|
|
|
|
FindButton("Restart Run").onClick.Invoke();
|
|
Assert.That(IsRestartConfirmationVisible(), Is.False);
|
|
Assert.That(world.RunGeneration, Is.EqualTo(generation));
|
|
|
|
Assert.That(hudTestProgression.TryChooseModifier(0), Is.True);
|
|
hudTestHud.RefreshState();
|
|
FindButton("Restart Run").onClick.Invoke();
|
|
Assert.That(IsRestartConfirmationVisible(), Is.True);
|
|
Assert.That(world.RunGeneration, Is.EqualTo(generation));
|
|
yield return null;
|
|
}
|
|
|
|
[UnityTest]
|
|
public IEnumerator ConfirmedLongRunRestartRebindsMinimapToTheNewLayout()
|
|
{
|
|
yield return InitializeHudTestWorld(longRun: true);
|
|
Defense25DComparison world = hudTestWorldObject.GetComponent<Defense25DComparison>();
|
|
world.AdvanceRunClock(world.PreparationRemaining + 0.1f);
|
|
world.SetPaused(true);
|
|
Defense25DRunLayout previousLayout = world.RunLayout;
|
|
int previousGeneration = world.RunGeneration;
|
|
|
|
FindButton("Restart Run").onClick.Invoke();
|
|
Assert.That(IsRestartConfirmationVisible(), Is.True);
|
|
FindButton("Confirm Restart").onClick.Invoke();
|
|
|
|
Assert.That(world.RunGeneration, Is.EqualTo(previousGeneration + 1));
|
|
Assert.That(world.RunLayout, Is.Not.SameAs(previousLayout));
|
|
hudTestHud.RefreshState();
|
|
FieldInfo cachedLayoutSeed = typeof(Defense25DRunHud).GetField("layoutSeed",
|
|
BindingFlags.Instance | BindingFlags.NonPublic);
|
|
Assert.That(cachedLayoutSeed.GetValue(hudTestHud),
|
|
Is.EqualTo(world.RunLayout.LayoutSeed),
|
|
"Restart must rebuild the HUD minimap from the newly generated layout.");
|
|
Assert.That(hudTestHud.IsRestartConfirmationPending, Is.False);
|
|
yield return null;
|
|
}
|
|
|
|
[UnityTest]
|
|
public IEnumerator ArtifactDropCandidateIsShownAndChosenThroughHudModal()
|
|
{
|
|
yield return InitializeHudTestWorld(longRun: true);
|
|
Defense25DComparison world = hudTestWorldObject.GetComponent<Defense25DComparison>();
|
|
world.AdvanceRunClock(20f);
|
|
Assert.That(world.RunPhase, Is.EqualTo(Defense25DRunPhase.Running));
|
|
world.AdvanceRunClock(60f);
|
|
Defense25DArtifactDropState drop = world.ArtifactDropStates[0];
|
|
world.Player.ResetAt(drop.Position);
|
|
Assert.That(world.TryInteractWithArtifactDrop(drop.DropId), Is.True);
|
|
Assert.That(world.CurrentArtifactDropCandidates, Has.Count.EqualTo(2));
|
|
|
|
hudTestHud.RefreshState();
|
|
Assert.That(FindButton("Choice 1").gameObject.activeInHierarchy, Is.True);
|
|
Assert.That(FindButton("Choice 2").gameObject.activeInHierarchy, Is.True);
|
|
FindButton("Choice 1").onClick.Invoke();
|
|
|
|
Assert.That(world.HasPendingArtifactDropChoice, Is.False);
|
|
Assert.That(world.Loadout.OwnedArtifactCount, Is.EqualTo(1));
|
|
Assert.That(FindButton("Choice 1").gameObject.activeInHierarchy, Is.False);
|
|
yield return null;
|
|
}
|
|
|
|
[UnityTest]
|
|
public IEnumerator PortalRowsSeparateTelegraphOpenAndStoppedUnopenedStates()
|
|
{
|
|
yield return InitializeHudTestWorld(longRun: true);
|
|
Defense25DComparison world = hudTestWorldObject.GetComponent<Defense25DComparison>();
|
|
world.AdvanceRunClock(130f);
|
|
hudTestHud.RefreshState();
|
|
|
|
Text thirdPortal = FindText("Portal 3");
|
|
Assert.That(thirdPortal.text, Does.Contain("예고 00:10"));
|
|
Assert.That(thirdPortal.text, Does.Contain("위협"));
|
|
|
|
world.AdvanceRunClock(10f);
|
|
hudTestHud.RefreshState();
|
|
Assert.That(world.WaveDirector.IsPortalOpen(2), Is.True);
|
|
Assert.That(thirdPortal.text, Does.Not.Contain("미개방"));
|
|
|
|
world.WaveDirector.StopAllSupply();
|
|
hudTestHud.RefreshState();
|
|
Text twelfthPortal = FindText("Portal 12");
|
|
Assert.That(FindText("Summary").text, Does.Contain("다음 강적 공급 중단"));
|
|
Assert.That(thirdPortal.text, Does.Contain("공급 중단"));
|
|
Assert.That(thirdPortal.text, Does.Not.Contain("미개방"));
|
|
Assert.That(twelfthPortal.text, Does.Contain("미개방"));
|
|
Assert.That(twelfthPortal.text, Does.Contain("공급 중단"));
|
|
yield return null;
|
|
}
|
|
|
|
[UnityTest]
|
|
public IEnumerator EarlyCallButtonUsesWorldCommandAndCurrencyShowsSpendableAndKillTotalsSeparately()
|
|
{
|
|
yield return InitializeHudTestWorld(longRun: true);
|
|
Defense25DComparison world = hudTestWorldObject.GetComponent<Defense25DComparison>();
|
|
|
|
world.AdvanceRunClock(37f);
|
|
hudTestHud.RefreshState();
|
|
Button earlyCall = FindButton("Early Call Next Wave");
|
|
Defense25DWavePlan currentPlan = world.WaveDirector.GetCurrentPlan();
|
|
Assert.That(currentPlan, Is.Not.Null);
|
|
Assert.That(world.CombatElapsed,
|
|
Is.LessThan(currentPlan.ActualStart + world.RunDefinition.WaveDirectorConstants.SpawnWindowSeconds));
|
|
Assert.That(earlyCall.interactable, Is.False,
|
|
"Early call must stay disabled during the current wave spawn window.");
|
|
Assert.That(FindText("Summary").text, Does.Contain("스폰 창"));
|
|
|
|
world.AdvanceRunClock(1f);
|
|
Assert.That(world.RunPhase, Is.EqualTo(Defense25DRunPhase.Running));
|
|
Assert.That(world.NotifyLongRunBossActivated(), Is.True);
|
|
|
|
world.TowerManager.AddCurrency(63);
|
|
hudTestHud.RefreshState();
|
|
Text summary = FindText("Summary");
|
|
Assert.That(summary.text, Does.Contain($"건설 재화 {world.TowerManager.ConstructionCurrency}"));
|
|
Assert.That(summary.text, Does.Contain($"처치 재화 {world.KillCurrency}"));
|
|
|
|
int currencyBeforeCall = world.TowerManager.ConstructionCurrency;
|
|
Assert.That(earlyCall.gameObject.activeInHierarchy, Is.True);
|
|
Assert.That(earlyCall.interactable, Is.True);
|
|
Assert.That(FindText("Summary").text, Does.Contain("Micro Break"));
|
|
earlyCall.onClick.Invoke();
|
|
Assert.That(world.TowerManager.ConstructionCurrency, Is.GreaterThan(currencyBeforeCall));
|
|
Assert.That(FindText("HUD Action Message").text, Does.Contain("조기 호출"));
|
|
Assert.That(FindText("Summary").text, Does.Contain("Micro Break 00:00"));
|
|
yield return null;
|
|
}
|
|
|
|
[UnityTest]
|
|
public IEnumerator RunSummaryShowsNextWaveThreatTelegraphAndBreakState()
|
|
{
|
|
yield return InitializeHudTestWorld(longRun: true);
|
|
Defense25DComparison world = hudTestWorldObject.GetComponent<Defense25DComparison>();
|
|
world.AdvanceRunClock(37f);
|
|
hudTestHud.RefreshState();
|
|
Assert.That(FindText("Summary").text, Does.Contain("스폰 창"));
|
|
|
|
world.AdvanceRunClock(1f);
|
|
hudTestHud.RefreshState();
|
|
|
|
Defense25DWavePlan current = world.WaveDirector.GetCurrentPlan();
|
|
Defense25DWavePlan next = world.WaveDirector.GetNextPlanPreview();
|
|
Assert.That(current, Is.Not.Null);
|
|
Assert.That(next, Is.Not.Null);
|
|
|
|
string summary = FindText("Summary").text;
|
|
Assert.That(summary, Does.Contain($"다음 Wave {next.WaveId}"));
|
|
Assert.That(summary, Does.Contain($"다음 위협 {next.ThreatBudget:0.#}"));
|
|
Assert.That(summary, Does.Contain("예고"));
|
|
Assert.That(summary, Does.Contain("Micro Break"));
|
|
|
|
Assert.That(world.WaveDirector.TryGetNextStrongEventPreview(
|
|
out Defense25DSpawnSource strongSource, out float nominalStrongStart), Is.True);
|
|
Assert.That(strongSource, Is.EqualTo(Defense25DSpawnSource.GlobalElite));
|
|
world.WaveDirector.RegisterReliefWindow("hud-next-strong-relief", nominalStrongStart, 12f);
|
|
Assert.That(world.WaveDirector.TryGetNextStrongEventPreview(out strongSource,
|
|
out float actualStrongStart), Is.True);
|
|
Assert.That(actualStrongStart, Is.GreaterThan(nominalStrongStart),
|
|
"The HUD preview must use the Director's relief-adjusted actual time.");
|
|
hudTestHud.RefreshState();
|
|
int strongCountdownSeconds = Mathf.Max(0,
|
|
Mathf.CeilToInt(actualStrongStart - world.CombatElapsed));
|
|
string expectedStrongCountdown =
|
|
$"{strongCountdownSeconds / 60:00}:{strongCountdownSeconds % 60:00}";
|
|
Assert.That(FindText("Summary").text,
|
|
Does.Contain($"다음 강적 엘리트 {expectedStrongCountdown}"));
|
|
|
|
world.WaveDirector.RegisterReliefWindow("hud-summary-relief",
|
|
world.CombatElapsed, 5f);
|
|
hudTestHud.RefreshState();
|
|
Assert.That(FindText("Summary").text, Does.Contain("구호 시간"));
|
|
Assert.That(FindButton("Early Call Next Wave").interactable, Is.False,
|
|
"Early call must be disabled while the Director is in a relief window.");
|
|
yield return null;
|
|
}
|
|
|
|
[UnityTest]
|
|
public IEnumerator RunSummaryUsesStrategyBreakScheduleWhenConfigured()
|
|
{
|
|
yield return InitializeHudTestWorld(longRun: true, strategyBreakEvery: 1);
|
|
Defense25DComparison world = hudTestWorldObject.GetComponent<Defense25DComparison>();
|
|
world.AdvanceRunClock(38f);
|
|
hudTestHud.RefreshState();
|
|
|
|
Assert.That(world.WaveDirector.GetCurrentPlan().WaveId, Is.EqualTo(1));
|
|
Assert.That(FindText("Summary").text, Does.Contain("Strategy Break 00:15"));
|
|
yield return null;
|
|
}
|
|
|
|
[UnityTest]
|
|
public IEnumerator MissionRowsShowRuntimeStateAndStartThroughWorldCommand()
|
|
{
|
|
yield return InitializeHudTestWorld(longRun: true, allPortalsOpen: true);
|
|
Defense25DComparison world = hudTestWorldObject.GetComponent<Defense25DComparison>();
|
|
Assert.That(world.PersonalMissionStates, Has.Count.EqualTo(3));
|
|
world.AdvanceRunClock(38f);
|
|
hudTestHud.RefreshState();
|
|
|
|
Button start = FindButton("Start Mission 1");
|
|
Assert.That(start.gameObject.activeInHierarchy, Is.True);
|
|
Assert.That(start.interactable, Is.True);
|
|
start.onClick.Invoke();
|
|
|
|
Assert.That(world.PersonalMissionStates[0].Status,
|
|
Is.EqualTo(Defense25DPersonalMissionStatus.Active));
|
|
Assert.That(FindText("Mission 1").text, Does.Contain("진행"));
|
|
Assert.That(start.gameObject.activeInHierarchy, Is.False);
|
|
yield return null;
|
|
}
|
|
|
|
[UnityTest]
|
|
public IEnumerator VictoryAndDefeatRestartImmediately()
|
|
{
|
|
yield return InitializeHudTestWorld(longRun: false);
|
|
Defense25DComparison world = hudTestWorldObject.GetComponent<Defense25DComparison>();
|
|
int generation = world.RunGeneration;
|
|
SetPrivateResult(world, "RunCleared", true);
|
|
hudTestHud.RefreshState();
|
|
FindButton("Restart Run").onClick.Invoke();
|
|
Assert.That(world.RunGeneration, Is.EqualTo(generation + 1));
|
|
Assert.That(IsRestartConfirmationVisible(), Is.False);
|
|
|
|
generation = world.RunGeneration;
|
|
SetPrivateResult(world, "RunFailed", true);
|
|
hudTestHud.RefreshState();
|
|
FindButton("Restart Run").onClick.Invoke();
|
|
Assert.That(world.RunGeneration, Is.EqualTo(generation + 1));
|
|
Assert.That(IsRestartConfirmationVisible(), Is.False);
|
|
yield return null;
|
|
}
|
|
|
|
[UnityTearDown]
|
|
public IEnumerator DestroyViewTestObjects()
|
|
{
|
|
if (hudTestKeyboard != null && hudTestKeyboard.added)
|
|
{
|
|
InputSystem.QueueStateEvent(hudTestKeyboard, new KeyboardState());
|
|
InputSystem.RemoveDevice(hudTestKeyboard);
|
|
}
|
|
hudTestKeyboard = null;
|
|
if (hudTestObject != null)
|
|
Object.Destroy(hudTestObject);
|
|
if (hudTestWorldObject != null)
|
|
Object.Destroy(hudTestWorldObject);
|
|
if (hudTestSettings != null)
|
|
Object.DestroyImmediate(hudTestSettings);
|
|
if (hudTestRunDefinition != null)
|
|
Object.DestroyImmediate(hudTestRunDefinition);
|
|
if (hudTestWaveConstants != null)
|
|
Object.DestroyImmediate(hudTestWaveConstants);
|
|
if (hudTestRewardConstants != null)
|
|
Object.DestroyImmediate(hudTestRewardConstants);
|
|
if (hudTestModifier != null)
|
|
Object.DestroyImmediate(hudTestModifier);
|
|
hudTestHud = null;
|
|
hudTestObject = null;
|
|
hudTestWorldObject = null;
|
|
hudTestSettings = null;
|
|
hudTestRunDefinition = null;
|
|
hudTestWaveConstants = null;
|
|
hudTestProgression = null;
|
|
hudTestRewardConstants = null;
|
|
hudTestModifier = null;
|
|
if (hudInputSettingsCaptured)
|
|
{
|
|
InputSystem.settings.backgroundBehavior = previousBackgroundBehavior;
|
|
InputSystem.settings.editorInputBehaviorInPlayMode =
|
|
previousEditorInputBehavior;
|
|
Application.runInBackground = previousRunInBackground;
|
|
hudInputSettingsCaptured = false;
|
|
}
|
|
Time.timeScale = previousTimeScale;
|
|
yield return null;
|
|
}
|
|
|
|
private Defense25DRunHud hudTestHud;
|
|
private Keyboard hudTestKeyboard;
|
|
|
|
private IEnumerator InitializeHudTestWorld(bool longRun, bool allPortalsOpen = false,
|
|
int strategyBreakEvery = 0)
|
|
{
|
|
Time.timeScale = 0f;
|
|
hudTestSettings = Object.Instantiate(AssetDatabase.LoadAssetAtPath<Defense25DComparisonSettings>(
|
|
"Assets/_Project/Constants/Defense25DComparison/ComparisonSettings.asset"));
|
|
hudTestWorldObject = new GameObject("Run HUD interaction world");
|
|
Defense25DComparison world = hudTestWorldObject.AddComponent<Defense25DComparison>();
|
|
yield return null;
|
|
if (longRun)
|
|
{
|
|
hudTestRunDefinition = Object.Instantiate(
|
|
AssetDatabase.LoadAssetAtPath<Defense25DRunDefinition>(
|
|
"Assets/_Project/Constants/Defense25DComparison/LongDefenseRun.asset"));
|
|
if (strategyBreakEvery > 0)
|
|
{
|
|
hudTestWaveConstants = Object.Instantiate(
|
|
hudTestRunDefinition.WaveDirectorConstants);
|
|
hudTestWaveConstants.StrategyBreakEvery = strategyBreakEvery;
|
|
hudTestRunDefinition.WaveDirectorConstants = hudTestWaveConstants;
|
|
}
|
|
hudTestRunDefinition.RewardConstants = AssetDatabase.LoadAssetAtPath<Defense25DRewardConstants>(
|
|
"Assets/_Project/Constants/Defense25DComparison/RewardConstants.asset");
|
|
EnemyDefinition testSpecialEnemy = AssetDatabase.LoadAssetAtPath<EnemyDefinition>(
|
|
"Assets/_Project/Constants/Enemies/Skeleton.asset");
|
|
Assert.That(testSpecialEnemy, Is.Not.Null,
|
|
"The long-run HUD fixture requires a valid source enemy definition.");
|
|
hudTestRunDefinition.GlobalEliteDefinition = testSpecialEnemy;
|
|
hudTestRunDefinition.GlobalMidBossDefinition = testSpecialEnemy;
|
|
hudTestRunDefinition.FinalBossDefinition = testSpecialEnemy;
|
|
hudTestRunDefinition.MonsterSourceMotionCatalog =
|
|
AssetDatabase.LoadAssetAtPath<Defense25DMonsterSourceMotionCatalog>(
|
|
"Assets/_Project/Constants/Defense25DComparison/MonsterSourceMotions.asset");
|
|
if (allPortalsOpen)
|
|
{
|
|
Defense25DPortalDefinition[] portals =
|
|
(Defense25DPortalDefinition[])hudTestRunDefinition.PortalDefinitions.Clone();
|
|
for (int i = 0; i < portals.Length; i++)
|
|
portals[i].OpenAt = 0f;
|
|
hudTestRunDefinition.PortalDefinitions = portals;
|
|
}
|
|
Assert.That(hudTestRunDefinition.MonsterSourceMotionCatalog, Is.Not.Null);
|
|
Assert.That(hudTestRunDefinition.Validate(out string validationError), Is.True,
|
|
validationError);
|
|
world.InitializeLongRun(hudTestSettings, hudTestRunDefinition);
|
|
}
|
|
else
|
|
world.Initialize(hudTestSettings);
|
|
world.AutoSpawn = false;
|
|
world.ReadInput = false;
|
|
hudTestObject = new GameObject("Run HUD interaction under test");
|
|
hudTestHud = hudTestObject.AddComponent<Defense25DRunHud>();
|
|
hudTestHud.Bind(world, null, null);
|
|
FieldInfo runHudField = typeof(Defense25DComparison).GetField("runHud",
|
|
BindingFlags.Instance | BindingFlags.NonPublic);
|
|
Assert.That(runHudField, Is.Not.Null,
|
|
"The HUD input fixture must use the world's actual presentation binding.");
|
|
runHudField.SetValue(world, hudTestHud);
|
|
yield return null;
|
|
}
|
|
|
|
private Button FindButton(string name)
|
|
{
|
|
foreach (Button button in hudTestObject.GetComponentsInChildren<Button>(true))
|
|
if (button.name == name)
|
|
return button;
|
|
Assert.Fail($"HUD button '{name}' was not created.");
|
|
return null;
|
|
}
|
|
|
|
private Text FindText(string name)
|
|
{
|
|
foreach (Text text in hudTestObject.GetComponentsInChildren<Text>(true))
|
|
if (text.name == name)
|
|
return text;
|
|
Assert.Fail($"HUD text '{name}' was not created.");
|
|
return null;
|
|
}
|
|
|
|
private static string GetButtonLabel(Button button)
|
|
{
|
|
Text label = button != null ? button.GetComponentInChildren<Text>(true) : null;
|
|
return label != null ? label.text : string.Empty;
|
|
}
|
|
|
|
private static int CountOccurrences(string source, string value)
|
|
{
|
|
if (string.IsNullOrEmpty(source) || string.IsNullOrEmpty(value))
|
|
return 0;
|
|
return (source.Length - source.Replace(value, string.Empty).Length) / value.Length;
|
|
}
|
|
|
|
private void BeginHudKeyboardInput()
|
|
{
|
|
if (hudTestKeyboard == null || !hudTestKeyboard.added)
|
|
{
|
|
hudInputSettingsCaptured = 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;
|
|
hudTestKeyboard = InputSystem.AddDevice<Keyboard>();
|
|
hudTestKeyboard.MakeCurrent();
|
|
}
|
|
}
|
|
|
|
private IEnumerator PressHudKey(Key key)
|
|
{
|
|
InputSystem.QueueStateEvent(hudTestKeyboard, new KeyboardState(key));
|
|
yield return null;
|
|
Assert.That(hudTestKeyboard[key].isPressed, Is.True,
|
|
$"Queued {key} input must reach the synthetic keyboard on frame {Time.frameCount}.");
|
|
Assert.That(Keyboard.current, Is.SameAs(hudTestKeyboard),
|
|
$"HUD input must read the same synthetic keyboard that received {key}.");
|
|
Assert.That(hudTestKeyboard[key].wasPressedThisFrame, Is.True,
|
|
$"Queued {key} press edge must be visible to HUD LateUpdate on frame {Time.frameCount}.");
|
|
|
|
// HUD key handling is in LateUpdate. A UnityTest resumed from `yield return null`
|
|
// can assert during Update before that LateUpdate has consumed the press edge.
|
|
// Resume one frame later so caller assertions test the resulting HUD state.
|
|
yield return null;
|
|
}
|
|
|
|
private IEnumerator ReleaseHudKeys()
|
|
{
|
|
InputSystem.QueueStateEvent(hudTestKeyboard, new KeyboardState());
|
|
yield return null;
|
|
Assert.That(hudTestKeyboard.anyKey.isPressed, Is.False);
|
|
Assert.That(Keyboard.current, Is.SameAs(hudTestKeyboard),
|
|
"The synthetic keyboard that received key releases must remain current.");
|
|
|
|
// The HUD clears its held-A safety latch in LateUpdate after observing release.
|
|
// Let that frame finish before the caller checks the post-modal input state.
|
|
yield return null;
|
|
}
|
|
|
|
private bool IsRestartConfirmationVisible()
|
|
{
|
|
foreach (Button button in hudTestObject.GetComponentsInChildren<Button>(true))
|
|
if (button.name == "Confirm Restart")
|
|
return button.gameObject.activeInHierarchy;
|
|
return false;
|
|
}
|
|
|
|
private static ModifierDefinition FindFirstLevelUpModifier()
|
|
{
|
|
ModifierDefinition[] definitions = GameplayConstants.Current.LevelUp.ModifierDefinitions;
|
|
if (definitions == null)
|
|
return null;
|
|
for (int i = 0; i < definitions.Length; i++)
|
|
if (definitions[i] != null)
|
|
return definitions[i];
|
|
return null;
|
|
}
|
|
|
|
private static void SetPrivateResult(Defense25DComparison world, string property, bool value)
|
|
{
|
|
typeof(Defense25DComparison).GetProperty(property)
|
|
.GetSetMethod(true).Invoke(world, new object[] { value });
|
|
if (property == "RunCleared")
|
|
typeof(Defense25DComparison).GetProperty("RunFailed")
|
|
.GetSetMethod(true).Invoke(world, new object[] { false });
|
|
else
|
|
typeof(Defense25DComparison).GetProperty("RunCleared")
|
|
.GetSetMethod(true).Invoke(world, new object[] { false });
|
|
}
|
|
|
|
}
|
|
}
|