using System.Collections.Generic; using System.Linq; using BumpCombat.Constants; using BumpCombat.Defense.Comparison; using BumpCombat.Enemies; using NUnit.Framework; using UnityEditor; using UnityEngine; namespace BumpCombat.Tests { public sealed class Defense25DMonsterCombatAdapterTests { private const string PoolPath = "Assets/_Project/Constants/Defense25DComparison/MonsterPools.asset"; [Test] public void EveryPoolEntryResolvesItsExactAuthoredAttackAction() { Defense25DMonsterPoolDefinition pool = AssetDatabase.LoadAssetAtPath(PoolPath); Assert.That(pool, Is.Not.Null); Assert.That(pool.TryValidate(out string error), Is.True, error); Texture2D texture = new Texture2D(1, 1); Sprite sourceFrame = Sprite.Create(texture, new Rect(0, 0, 1, 1), new Vector2(.5f, .5f)); try { foreach (Defense25DMonsterPoolEntry entry in pool.Tiers.SelectMany(tier => tier.Entries)) { string action = entry.CombatProfile.AttackAction; var actions = new Dictionary { [action] = new[] { sourceFrame }, }; var motion = new Defense25DMonsterSourceMotion( entry.SourceCharacterId, new[] { sourceFrame }, new[] { sourceFrame }, actions, null, .1f); Assert.That(Defense25DMonsterCombatAdapter.TryResolveAttackFrames( entry, motion, out Sprite[] resolved, out string resolveError), Is.True, $"{entry.EnemyDefinitionId}: {resolveError}"); Assert.That(resolved, Is.EqualTo(new[] { sourceFrame }), entry.EnemyDefinitionId); } } finally { Object.DestroyImmediate(sourceFrame); Object.DestroyImmediate(texture); } } [Test] public void SourceMotionRejectsWrongSpeciesAndNeverFallsBackToSkeletonFrames() { Defense25DMonsterPoolDefinition pool = AssetDatabase.LoadAssetAtPath(PoolPath); Defense25DMonsterPoolEntry entry = pool.Tiers.SelectMany(tier => tier.Entries) .First(value => value.EnemyDefinitionId == "Demon_A"); Texture2D texture = new Texture2D(1, 1); Sprite skeletonFrame = Sprite.Create(texture, new Rect(0, 0, 1, 1), new Vector2(.5f, .5f)); try { var wrongSpecies = new Defense25DMonsterSourceMotion( "Skeleton", new[] { skeletonFrame }, new[] { skeletonFrame }, new Dictionary { ["Attack"] = new[] { skeletonFrame } }, null, .1f); Assert.That(Defense25DMonsterCombatAdapter.TryResolveAttackFrames( entry, wrongSpecies, out _, out string speciesError), Is.False); Assert.That(speciesError, Does.Contain("SourceCharacterId")); var missingAction = new Defense25DMonsterSourceMotion( entry.SourceCharacterId, new[] { skeletonFrame }, new[] { skeletonFrame }, new Dictionary { ["Attack"] = new[] { skeletonFrame } }, null, .1f); Assert.That(Defense25DMonsterCombatAdapter.TryResolveAttackFrames( entry, missingAction, out _, out string actionError), Is.False); Assert.That(actionError, Does.Contain(entry.CombatProfile.AttackAction)); } finally { Object.DestroyImmediate(skeletonFrame); Object.DestroyImmediate(texture); } } [TestCase(EnemyAttackShape.Body, 0.2f, 0f, 0f, true)] [TestCase(EnemyAttackShape.Body, 0.4f, 0f, 0f, false)] [TestCase(EnemyAttackShape.Box, 0.8f, 0.2f, 1f, true)] [TestCase(EnemyAttackShape.Box, -0.2f, 0f, 1f, false)] [TestCase(EnemyAttackShape.Cone, 1f, 0.2f, 2f, true)] [TestCase(EnemyAttackShape.Cone, 1f, 0.8f, 2f, false)] [TestCase(EnemyAttackShape.TargetCircle, 1.9f, 0.2f, .5f, true)] [TestCase(EnemyAttackShape.TargetCircle, .5f, 0.2f, .5f, false)] public void AttackGeometryUsesItsAuthoredShape( EnemyAttackShape shape, float targetX, float targetY, float lengthOrRadius, bool expected) { bool hit = Defense25DMonsterCombatAdapter.IntersectsAttack( shape, Vector2.zero, Vector2.right, new Vector2(2f, 0f), new Vector2(targetX, targetY), targetRadius: .1f, bodyRadius: .25f, attackLength: lengthOrRadius, attackWidth: .6f, attackRadius: lengthOrRadius); Assert.That(hit, Is.EqualTo(expected)); } [Test] public void ProjectileSweptHitUsesTheWholeSegmentAndConfiguredRadius() { Assert.That(Defense25DMonsterCombatAdapter.ProjectileSegmentHits( Vector2.zero, Vector2.right * 2f, new Vector2(1f, .09f), .1f), Is.True); Assert.That(Defense25DMonsterCombatAdapter.ProjectileSegmentHits( Vector2.zero, Vector2.right * 2f, new Vector2(1f, .12f), .1f), Is.False); } [Test] public void AdapterExposesTheRunSpawnLifecycleContract() { Assert.That(typeof(Defense25DMonsterCombatAdapter).GetMethod("Bind"), Is.Not.Null); Assert.That(typeof(Defense25DMonsterCombatAdapter).GetMethod("Step"), Is.Not.Null); Assert.That(typeof(Defense25DMonsterCombatAdapter).GetMethod("Cancel"), Is.Not.Null); } } }