using System.Collections; using System.Reflection; using BumpCombat.Defense.Comparison; using NUnit.Framework; using UnityEngine; using UnityEngine.TestTools; using Object = UnityEngine.Object; namespace BumpCombat.Tests { public sealed class Defense25DStatusVisualPlayModeTests { private GameObject enemyObject; private GameObject actorObject; private GameObject spriteObject; private GameObject worldObject; [UnityTearDown] public IEnumerator TearDown() { if (worldObject != null) Object.Destroy(worldObject); else { if (enemyObject != null) Object.Destroy(enemyObject); if (actorObject != null) Object.Destroy(actorObject); if (spriteObject != null) Object.Destroy(spriteObject); } yield return null; } [UnityTest] public IEnumerator ActiveStatusesRenderExistingFramesAndClearWhenTheirTimersExpire() { const string ignitePath = "Artifacts/ThreeColor-v1/StatusEffects/Ignite-Warlock-v1"; Sprite[] importedFrames = Resources.LoadAll(ignitePath); Texture2D igniteTexture = importedFrames.Length > 0 ? importedFrames[0].texture : Resources.Load(ignitePath); Assert.That(igniteTexture, Is.Not.Null); worldObject = new GameObject("Status visual world"); Defense25DComparison owner = worldObject.AddComponent(); SetBackingField(owner, "IsRunning", true); SetBackingField(owner, "Paused", true); spriteObject = new GameObject("Status visual source"); SpriteRenderer sourceRenderer = spriteObject.AddComponent(); actorObject = new GameObject("Status visual actor"); actorObject.transform.SetParent(worldObject.transform, false); spriteObject.transform.SetParent(actorObject.transform, false); spriteObject.transform.rotation = Quaternion.Euler(30f, 0f, 0f); sourceRenderer.sortingOrder = 12; sourceRenderer.flipX = true; sourceRenderer.color = new Color(1f, 1f, 1f, .4f); Defense25DActor actor = actorObject.AddComponent(); SetBackingField(actor, "CurrentHealth", 10f); Defense25DSprite actorVisual = actorObject.AddComponent(); SetPrivateField(actorVisual, "sprite", sourceRenderer); actorVisual.enabled = false; SetBackingField(actor, "Visual", actorVisual); enemyObject = new GameObject("Status visual enemy"); enemyObject.transform.SetParent(worldObject.transform, false); Defense25DEnemy enemy = enemyObject.AddComponent(); SetBackingField(enemy, "Actor", actor); SetPrivateField(enemy, "world", owner); SetPrivateField(enemy, "igniteRemaining", .5f); SetPrivateField(enemy, "shockRemaining", .5f); SetPrivateField(enemy, "stunUntil", .5f); SetPrivateField(enemy, "stunVisualRevision", 1); Defense25DStatusVisual statusVisual = enemyObject.AddComponent(); statusVisual.Bind(enemy); statusVisual.SendMessage("LateUpdate"); Transform igniteVisual = spriteObject.transform.Find("Ignite Status Visual"); Transform shockVisual = spriteObject.transform.Find("Shock Status Visual"); Assert.That(igniteVisual, Is.Not.Null); Assert.That(shockVisual, Is.Not.Null); Assert.That(enemy.IsIgnited, Is.True); Assert.That(enemy.IsShocked, Is.True); Assert.That(GetPrivateField(GetPrivateField(statusVisual, "ignite"), "FrameElapsed"), Is.EqualTo(0f)); Assert.That(GetPrivateField(GetPrivateField(statusVisual, "shock"), "FrameElapsed"), Is.EqualTo(0f)); Assert.That(GetPrivateField(statusVisual, "stunElapsed"), Is.EqualTo(0f)); Transform stunMarker = actorObject.transform.Find("Stun Runes"); Assert.That(stunMarker, Is.Not.Null); SpriteRenderer stunRenderer = stunMarker.GetComponent(); Assert.That(stunRenderer.sharedMaterial, Is.SameAs(sourceRenderer.sharedMaterial)); Assert.That(stunRenderer.color.a, Is.EqualTo(sourceRenderer.color.a).Within(.001f)); Assert.That(stunMarker.localPosition.z, Is.GreaterThan(0f)); SpriteRenderer igniteRenderer = igniteVisual.GetComponent(); Assert.That(igniteRenderer.sprite, Is.Not.Null); Assert.That(igniteRenderer.sprite.texture, Is.SameAs(igniteTexture)); Assert.That(igniteRenderer.sortingOrder, Is.EqualTo(sourceRenderer.sortingOrder + 1)); Assert.That(igniteRenderer.flipX, Is.EqualTo(sourceRenderer.flipX)); Assert.That(igniteRenderer.color.a, Is.EqualTo(sourceRenderer.color.a).Within(.001f)); Assert.That(igniteVisual.localPosition.y, Is.EqualTo(Defense25DStatusVisual.StatusVisualAnchorOffsetY).Within(.001f)); SetPrivateField(enemy, "igniteRemaining", 0f); SetPrivateField(enemy, "shockRemaining", 0f); statusVisual.SendMessage("LateUpdate"); Assert.That(enemy.IsIgnited, Is.False); Assert.That(enemy.IsShocked, Is.False); Assert.That(GetPrivateField(GetPrivateField(statusVisual, "ignite"), "Root"), Is.Null); Assert.That(GetPrivateField(GetPrivateField(statusVisual, "shock"), "Root"), Is.Null); SetPrivateField(enemy, "igniteRemaining", .5f); SetBackingField(owner, "Paused", false); statusVisual.SendMessage("LateUpdate"); Assert.That(GetPrivateField(GetPrivateField(statusVisual, "ignite"), "Root"), Is.Not.Null); SetBackingField(owner, "IsRunning", false); statusVisual.SendMessage("LateUpdate"); Assert.That(GetPrivateField(GetPrivateField(statusVisual, "ignite"), "Root"), Is.Null); SetBackingField(owner, "IsRunning", true); statusVisual.SendMessage("LateUpdate"); Assert.That(GetPrivateField(GetPrivateField(statusVisual, "ignite"), "Root"), Is.Not.Null); owner.enabled = false; statusVisual.SendMessage("LateUpdate"); Assert.That(GetPrivateField(GetPrivateField(statusVisual, "ignite"), "Root"), Is.Null); yield return null; } private static void SetBackingField(object target, string propertyName, object value) => SetPrivateField(target, $"<{propertyName}>k__BackingField", value); private static void SetPrivateField(object target, string fieldName, object value) { FieldInfo field = target.GetType().GetField( fieldName, BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public); Assert.That(field, Is.Not.Null, $"Missing test field {fieldName}"); field.SetValue(target, value); } private static object GetPrivateField(object target, string fieldName) { FieldInfo field = target.GetType().GetField( fieldName, BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public); Assert.That(field, Is.Not.Null, $"Missing test field {fieldName}"); return field.GetValue(target); } } }