using System.Collections; using System.Linq; using BumpCombat.Combat; using BumpCombat.Constants; using BumpCombat.Defense; using BumpCombat.Defense.Comparison; using BumpCombat.Player; using BumpCombat.Progression; using NUnit.Framework; using UnityEditor; using UnityEngine; using UnityEngine.TestTools; using Object = UnityEngine.Object; namespace BumpCombat.Tests { public sealed class Defense25DComparisonPresentationPlayModeTests { private Defense25DComparison world; private Defense25DComparisonSettings settings; private float previousTimeScale; private const string SettingsPath = "Assets/_Project/Constants/Defense25DComparison/ComparisonSettings.asset"; [UnitySetUp] public IEnumerator SetUp() { previousTimeScale = Time.timeScale; Time.timeScale = 1f; settings = Object.Instantiate(AssetDatabase.LoadAssetAtPath(SettingsPath)); Assert.That(settings, Is.Not.Null); settings.Enemy = AssetDatabase.LoadAssetAtPath( "Assets/_Project/Constants/Enemies/Slime.asset"); settings.EnemyArt = ReadCharacter(); world = new GameObject("Presentation integration world").AddComponent(); world.Initialize(settings); world.AutoSpawn = false; world.ReadInput = false; yield return new WaitForFixedUpdate(); } [UnityTearDown] public IEnumerator TearDown() { if (world != null) Object.Destroy(world.gameObject); if (settings != null) Object.Destroy(settings); Time.timeScale = previousTimeScale; yield return null; } [UnityTest] public IEnumerator BuildCatalogUsesAllSixRealTowerSpritesAndBuildsSelectedTypeAtSelectedPad() { Assert.That(world.TowerShowcaseKinds.Count(), Is.EqualTo(6)); foreach (DefenseTowerKind kind in world.TowerShowcaseKinds) Assert.That(world.GetTowerShowcaseSprite(kind), Is.Not.Null, $"Missing actual idle art for {kind}"); const int pad = 2; Vector2 position = GameplayConstants.Current.Defense.GetBuildPadPosition(pad); world.Player.ResetAt(position); Assert.That(world.SelectBuildKind(DefenseTowerKind.Lightning), Is.True); Assert.That(world.SelectBuildPad(pad), Is.True); int before = world.TowerManager.ConstructionCurrency; Assert.That(world.BuildSelectedTower(), Is.True); Defense25DTower tower = world.TowerManager.GetTowerAtPad(pad); Assert.That(tower, Is.Not.Null); Assert.That(tower.Kind, Is.EqualTo(DefenseTowerKind.Lightning)); Assert.That(world.TowerManager.ConstructionCurrency, Is.LessThan(before)); yield return null; } [UnityTest] public IEnumerator SelectedTowerOffersOnlyLegalTierAndFinalBranchUpgrades() { const int pad = 0; world.Player.ResetAt(GameplayConstants.Current.Defense.GetBuildPadPosition(pad)); world.TowerManager.AddCurrency(1000); Assert.That(world.SelectBuildPad(pad), Is.True); Assert.That(world.BuildSelectedTower(), Is.True); Assert.That(world.UpgradeSelectedTower(DefenseTowerBranch.First), Is.False, "Branches are only selected when upgrading from tier three."); Assert.That(world.UpgradeSelectedTower(DefenseTowerBranch.None), Is.True); Assert.That(world.TowerManager.GetTowerAtPad(pad).Tier, Is.EqualTo(2)); Assert.That(world.UpgradeSelectedTower(DefenseTowerBranch.None), Is.True); Assert.That(world.TowerManager.GetTowerAtPad(pad).Tier, Is.EqualTo(3)); Assert.That(world.UpgradeSelectedTower(DefenseTowerBranch.Second), Is.True); Defense25DTower tierFour = world.TowerManager.GetTowerAtPad(pad); Assert.That(tierFour.Tier, Is.EqualTo(4)); Assert.That(tierFour.Branch, Is.EqualTo(DefenseTowerBranch.Second)); Assert.That(world.UpgradeSelectedTower(DefenseTowerBranch.First), Is.False); yield return null; } [UnityTest] public IEnumerator RelicCannotBeGrantedOutsideACollectedPortalReward() { PassiveRelicId available = world.Loadout.GetAvailableRelics().First(); Assert.That(world.QueueFieldRelicRewardForCollectedPortal(0), Is.False); Assert.That(world.HasPendingFieldRelicReward, Is.False); Assert.That(world.ResolveFieldRelicChoice(available), Is.False); Assert.That(world.Loadout.OwnsRelic(available), Is.False); Assert.That(world.Paused, Is.False); yield return null; } [UnityTest] public IEnumerator PresentationPointerRegionsConsumeUiClicksButLeaveArenaPointerFree() { Rect viewport = Defense25DView.GetViewport(Screen.width, Screen.height); float scale = viewport.width / Defense25DView.Width; Vector2 ScreenPoint(float x, float y) => new(viewport.x + x * scale, Screen.height - (viewport.y + y * scale)); foreach (Rect control in world.GetPersistentDefenseUiHitRects()) Assert.That(world.IsPointerOverDefenseUI(ScreenPoint(control.center.x, control.center.y)), Is.True, $"Clicks on persistent HUD control {control} must not also operate the arena."); Assert.That(world.SelectBuildPad(0), Is.True); Rect details = world.GetSelectedPadDetailsRect(); Assert.That(details.width, Is.GreaterThan(0f)); Assert.That(world.IsPointerOverDefenseUI(ScreenPoint(details.center.x, details.center.y)), Is.True, "Clicks in the selected pad's contextual panel must not reach the arena."); Assert.That(world.IsPointerOverDefenseUI(ScreenPoint(320, 180)), Is.False, "The central field remains available for aiming and movement."); yield return null; } [UnityTest] public IEnumerator ShowcaseModeUsesDedicatedEntryAndExitHooks() { int entered = 0; int exited = 0; world.BindValidationShowcase(() => { entered++; return true; }, () => exited++); Assert.That(world.EnterValidationShowcase(), Is.True); Assert.That(world.IsValidationShowcaseActive, Is.True); Assert.That(world.EnterValidationShowcase(), Is.False); Assert.That(entered, Is.EqualTo(1)); Assert.That(world.ExitValidationShowcase(), Is.True); Assert.That(world.IsValidationShowcaseActive, Is.False); Assert.That(exited, Is.EqualTo(1)); yield return null; } private static ComparisonSpriteFrames ReadCharacter() { Sprite[] Read(string suffix) => AssetDatabase.LoadAllAssetsAtPath( $"Assets/_Project/Art/Characters/Slime/Slime_{suffix}.png") .OfType() .OrderBy(sprite => int.TryParse(sprite.name[(sprite.name.LastIndexOf('_') + 1)..], out int frame) ? frame : 0) .ToArray(); return new ComparisonSpriteFrames { Idle = Read("Idle"), Walk = Read("Walk"), Attack = Read("Attack"), Hurt = Read("Hurt"), Death = Read("Death"), }; } } }