130 lines
5.9 KiB
C#
130 lines
5.9 KiB
C#
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<Defense25DMonsterPoolDefinition>(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<string, Sprite[]>
|
|
{
|
|
[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<Defense25DMonsterPoolDefinition>(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<string, Sprite[]> { ["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<string, Sprite[]> { ["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);
|
|
}
|
|
}
|
|
}
|