using System.Collections; using BumpCombat.Core; using BumpCombat.Player; using BumpCombat.Stage; using NUnit.Framework; using UnityEngine; using UnityEngine.TestTools; namespace BumpCombat.PlayModeTests { public sealed class ZoneArrivalPlayModeTests { private GameObject arenaObject; private GameObject playerObject; private GameObject stageObject; private RunManager existingRunManager; private bool existingRunManagerWasEnabled; private float previousTimeScale; [SetUp] public void SetUp() { previousTimeScale = Time.timeScale; existingRunManager = Object.FindAnyObjectByType(); if (existingRunManager != null) { existingRunManagerWasEnabled = existingRunManager.enabled; existingRunManager.enabled = false; } Time.timeScale = 1f; } [TearDown] public void TearDown() { if (stageObject != null) { Object.Destroy(stageObject); } if (playerObject != null) { Object.Destroy(playerObject); } if (arenaObject != null) { Object.Destroy(arenaObject); } if (existingRunManager != null) { existingRunManager.enabled = existingRunManagerWasEnabled; } Time.timeScale = previousTimeScale; } [Timeout(60000)] [UnityTest] public IEnumerator ArrivalDoor_RejectsImmediateReverseAndRetriesOnStayAfterCooldown() { arenaObject = new GameObject("ArrivalPlayMode_ArenaBounds"); arenaObject.AddComponent(); playerObject = new GameObject("ArrivalPlayMode_Player"); PlayerController player = playerObject.AddComponent(); CircleCollider2D playerBody = playerObject.AddComponent(); playerBody.radius = 0.22f; playerBody.offset = new Vector2(0.11f, -0.07f); Rigidbody2D rigidbody = playerObject.GetComponent(); rigidbody.bodyType = RigidbodyType2D.Kinematic; rigidbody.gravityScale = 0f; rigidbody.constraints = RigidbodyConstraints2D.FreezeRotation; playerObject.GetComponent().enabled = false; player.enabled = false; stageObject = new GameObject("ArrivalPlayMode_StageManager"); StageManager stageManager = stageObject.AddComponent(); stageManager.StartRun(4242); // StageManager.Start also builds its autonomous test stage. Let // that lifecycle pass before taking connector references. yield return null; ZoneInstance startZone = stageManager.CurrentZone; Assert.That(startZone, Is.Not.Null); ZoneConnector sourceConnector = FindOutgoingConnector(stageManager); Assert.That(sourceConnector, Is.Not.Null); yield return new WaitForSecondsRealtime(0.4f); MoveBodyCenter(player, playerBody, sourceConnector.GetComponent().bounds.center); yield return new WaitForFixedUpdate(); int targetNodeId = sourceConnector.TargetNodeId; float transitionDeadline = Time.realtimeSinceStartup + 2f; while ((stageManager.IsTransitioning || stageManager.CurrentZone == null || stageManager.CurrentZone.NodeId != targetNodeId) && Time.realtimeSinceStartup < transitionDeadline) { yield return null; } Assert.That(stageManager.CurrentZone.NodeId, Is.EqualTo(targetNodeId)); Assert.That(stageManager.IsTransitioning, Is.False); ZoneConnector arrivalConnector = stageManager.CurrentZone.GetConnector(sourceConnector.TargetSocketId); Assert.That(arrivalConnector, Is.Not.Null); arrivalConnector.SetLocked(false); BoxCollider2D arrivalTrigger = arrivalConnector.GetComponent(); Assert.That(arrivalTrigger.bounds.Intersects(playerBody.bounds), Is.False); // Enter the destination trigger immediately after transition. The // trigger stay must be ignored until the manager cooldown expires. MoveBodyCenter(player, playerBody, arrivalTrigger.bounds.center); yield return new WaitForFixedUpdate(); yield return new WaitForSecondsRealtime(0.1f); Assert.That(stageManager.CurrentZone.NodeId, Is.EqualTo(targetNodeId)); float returnDeadline = Time.realtimeSinceStartup + 2f; while ((stageManager.IsTransitioning || stageManager.CurrentZone == null || stageManager.CurrentZone.NodeId != startZone.NodeId) && Time.realtimeSinceStartup < returnDeadline) { yield return null; } Assert.That(stageManager.CurrentZone.NodeId, Is.EqualTo(startZone.NodeId)); // The return arrival is also outside its doorway. Leaving the // player there must not produce an automatic pingpong transition. yield return new WaitForSecondsRealtime(0.45f); Assert.That(stageManager.CurrentZone.NodeId, Is.EqualTo(startZone.NodeId)); } private static ZoneConnector FindOutgoingConnector(StageManager stageManager) { for (int direction = 0; direction < 4; direction++) { ZoneConnector connector = stageManager.CurrentZone.GetConnector((StageDirection)direction); if (connector != null && connector.TargetNodeId >= 0) { return connector; } } return null; } private static void MoveBodyCenter( PlayerController player, Collider2D playerBody, Vector2 bodyCenter) { Vector2 bodyOffset = (Vector2)playerBody.bounds.center - (Vector2)player.transform.position; player.transform.position = bodyCenter - bodyOffset; Physics2D.SyncTransforms(); } } }