263 lines
12 KiB
C#
263 lines
12 KiB
C#
using System.Collections;
|
|
using System.Collections.Generic;
|
|
using BumpCombat.Constants;
|
|
using BumpCombat.Defense.Comparison;
|
|
using NUnit.Framework;
|
|
using UnityEditor;
|
|
using UnityEngine;
|
|
using UnityEngine.TestTools;
|
|
|
|
namespace BumpCombat.Tests
|
|
{
|
|
public sealed class Defense25DArtifactDropPlayModeTests
|
|
{
|
|
private sealed class ScheduleRecord
|
|
{
|
|
public readonly List<int> SpawnOrder = new();
|
|
public readonly List<(string id, float start, float duration)> Reliefs = new();
|
|
}
|
|
|
|
private GameObject worldObject;
|
|
private Defense25DComparison world;
|
|
private Defense25DArtifactDropConstants settings;
|
|
private Defense25DComparisonSettings comparisonSettings;
|
|
private Defense25DRunDefinition runDefinition;
|
|
private float timeScaleBeforeInteractionTest;
|
|
private bool restoreInteractionTestTimeScale;
|
|
|
|
[SetUp]
|
|
public void SetUp()
|
|
{
|
|
worldObject = new GameObject("Artifact drop test world");
|
|
world = worldObject.AddComponent<Defense25DComparison>();
|
|
settings = ScriptableObject.CreateInstance<Defense25DArtifactDropConstants>();
|
|
}
|
|
|
|
[TearDown]
|
|
public void TearDown()
|
|
{
|
|
if (restoreInteractionTestTimeScale)
|
|
{
|
|
Time.timeScale = timeScaleBeforeInteractionTest;
|
|
restoreInteractionTestTimeScale = false;
|
|
Assert.That(Time.timeScale, Is.EqualTo(timeScaleBeforeInteractionTest));
|
|
Debug.Log($"[ArtifactDropInteraction] teardown restored timeScale={Time.timeScale}");
|
|
}
|
|
|
|
if (worldObject != null) Object.DestroyImmediate(worldObject);
|
|
if (settings != null) Object.DestroyImmediate(settings);
|
|
if (comparisonSettings != null) Object.DestroyImmediate(comparisonSettings);
|
|
if (runDefinition != null) Object.DestroyImmediate(runDefinition);
|
|
}
|
|
|
|
[Test]
|
|
public void DropsUseFirstActualPortalOpenAndRegisterEachReliefWindowOnlyOnce()
|
|
{
|
|
Assert.That(Defense25DBattlefieldDefinition.CreateDefault().TryCreateLayout(
|
|
8026, out Defense25DRunLayout layout, out string error), Is.True, error);
|
|
Defense25DArtifactDrops drops = new();
|
|
drops.Bind(world, layout, settings, 419);
|
|
List<(string id, float start, float duration)> reliefs = new();
|
|
int spawned = 0;
|
|
drops.ReliefWindowRequested += (id, start, duration) => reliefs.Add((id, start, duration));
|
|
drops.DropSpawned += _ => spawned++;
|
|
|
|
drops.NotifyPortalOpened(0, 12.5f);
|
|
drops.NotifyPortalOpened(1, 20f);
|
|
drops.Step(72.49f);
|
|
Assert.That(drops.DropStates[0].Status, Is.EqualTo(Defense25DArtifactDropStatus.Scheduled));
|
|
|
|
drops.Step(72.5f);
|
|
Assert.That(drops.DropStates[0].Status, Is.EqualTo(Defense25DArtifactDropStatus.Available));
|
|
Assert.That(drops.DropStates[0].AvailableAt, Is.EqualTo(72.5f).Within(.001f));
|
|
|
|
drops.Step(360f);
|
|
drops.Step(360f);
|
|
|
|
Assert.That(spawned, Is.EqualTo(3));
|
|
Assert.That(drops.DropStates.Count, Is.EqualTo(3));
|
|
Assert.That(drops.DropStates[1].Status, Is.EqualTo(Defense25DArtifactDropStatus.Available));
|
|
Assert.That(drops.DropStates[2].Status, Is.EqualTo(Defense25DArtifactDropStatus.Available));
|
|
Assert.That(reliefs, Is.EqualTo(new[]
|
|
{
|
|
("artifact-drop-0-0", 72.5f, 12f),
|
|
("artifact-drop-0-1", 180f, 15f),
|
|
("artifact-drop-0-2", 360f, 15f),
|
|
}));
|
|
}
|
|
|
|
[Test]
|
|
public void LargeDeltaAndOneSecondStepsKeepReliefWindowsAtTheirScheduledTimes()
|
|
{
|
|
Assert.That(Defense25DBattlefieldDefinition.CreateDefault().TryCreateLayout(
|
|
6028, out Defense25DRunLayout layout, out string error), Is.True, error);
|
|
|
|
ScheduleRecord largeDelta = CaptureSchedule(layout, useOneSecondSteps: false);
|
|
ScheduleRecord smallSteps = CaptureSchedule(layout, useOneSecondSteps: true);
|
|
|
|
Assert.That(largeDelta.SpawnOrder, Is.EqualTo(new[] { 0, 1 }));
|
|
Assert.That(largeDelta.Reliefs, Is.EqualTo(new[]
|
|
{
|
|
("artifact-drop-0-0", 60f, 12f),
|
|
("artifact-drop-0-1", 180f, 15f),
|
|
}));
|
|
Assert.That(smallSteps.SpawnOrder, Is.EqualTo(largeDelta.SpawnOrder));
|
|
Assert.That(smallSteps.Reliefs, Is.EqualTo(largeDelta.Reliefs));
|
|
Assert.That(largeDelta.Reliefs[0].start, Is.InRange(60f, 72f));
|
|
Assert.That(largeDelta.Reliefs[1].start, Is.InRange(180f, 195f));
|
|
}
|
|
|
|
[Test]
|
|
public void LateFirstPortalDropIsEmittedAfterEarlierScheduledLaterDrop()
|
|
{
|
|
Assert.That(Defense25DBattlefieldDefinition.CreateDefault().TryCreateLayout(
|
|
6029, out Defense25DRunLayout layout, out string error), Is.True, error);
|
|
Defense25DArtifactDrops drops = new();
|
|
drops.Bind(world, layout, settings, 419);
|
|
ScheduleRecord record = new();
|
|
drops.DropSpawned += state => record.SpawnOrder.Add(state.DropId);
|
|
drops.ReliefWindowRequested += (id, start, duration) => record.Reliefs.Add((id, start, duration));
|
|
|
|
drops.NotifyPortalOpened(0, 130f);
|
|
drops.Step(200f);
|
|
|
|
Assert.That(drops.DropStates[0].AvailableAt, Is.EqualTo(190f).Within(.001f));
|
|
Assert.That(drops.DropStates[1].AvailableAt, Is.EqualTo(180f).Within(.001f));
|
|
Assert.That(record.SpawnOrder, Is.EqualTo(new[] { 1, 0 }));
|
|
Assert.That(record.Reliefs, Is.EqualTo(new[]
|
|
{
|
|
("artifact-drop-0-1", 180f, 15f),
|
|
("artifact-drop-0-0", 190f, 12f),
|
|
}));
|
|
}
|
|
|
|
[UnityTest]
|
|
public IEnumerator InteractedDropSnapshotsCandidatesAndClaimsOnlyTheChosenArtifactOnce()
|
|
{
|
|
timeScaleBeforeInteractionTest = Time.timeScale;
|
|
restoreInteractionTestTimeScale = true;
|
|
Debug.Log($"[ArtifactDropInteraction] start frame={Time.frameCount} timeScale={timeScaleBeforeInteractionTest}");
|
|
Time.timeScale = 1f;
|
|
|
|
comparisonSettings = Object.Instantiate(AssetDatabase.LoadAssetAtPath<Defense25DComparisonSettings>(
|
|
"Assets/_Project/Constants/Defense25DComparison/ComparisonSettings.asset"));
|
|
Assert.That(comparisonSettings, Is.Not.Null);
|
|
runDefinition = Object.Instantiate(AssetDatabase.LoadAssetAtPath<Defense25DRunDefinition>(
|
|
"Assets/_Project/Constants/Defense25DComparison/LongDefenseRun.asset"));
|
|
Assert.That(runDefinition, Is.Not.Null);
|
|
runDefinition.PreparationSeconds = 0f;
|
|
world.InitializeLongRun(comparisonSettings, runDefinition, layoutSeed: 1741);
|
|
world.AutoSpawn = false;
|
|
world.ReadInput = false;
|
|
Debug.Log($"[ArtifactDropInteraction] before null frame frame={Time.frameCount} timeScale={Time.timeScale}");
|
|
yield return null;
|
|
Debug.Log($"[ArtifactDropInteraction] before fixed update frame={Time.frameCount} timeScale={Time.timeScale}");
|
|
yield return new WaitForFixedUpdate();
|
|
Debug.Log($"[ArtifactDropInteraction] after fixed update frame={Time.frameCount} timeScale={Time.timeScale}");
|
|
Assert.That(world.RunPhase, Is.EqualTo(Defense25DRunPhase.Running));
|
|
|
|
Defense25DArtifactDrops drops = new();
|
|
drops.Bind(world, world.RunLayout, settings, 508);
|
|
world.Player.ResetAt(drops.DropStates[0].Position);
|
|
drops.NotifyPortalOpened(0, 0f);
|
|
drops.Step(60f);
|
|
|
|
Assert.That(drops.TryInteract(0), Is.True);
|
|
IReadOnlyList<BumpCombat.Player.ActiveArtifactDefinition> snapshot = drops.CurrentCandidates;
|
|
int generation = drops.RunGeneration;
|
|
int token = drops.CurrentChoiceToken;
|
|
Assert.That(snapshot, Has.Count.EqualTo(2));
|
|
Assert.That(snapshot[0].Effect, Is.Not.EqualTo(snapshot[1].Effect));
|
|
Assert.That(drops.TryInteract(0), Is.False);
|
|
Assert.That(drops.TryChoose(0, generation + 1, token), Is.False);
|
|
|
|
Assert.That(drops.TryChoose(0, generation, token), Is.True);
|
|
Assert.That(drops.DropStates[0].Status, Is.EqualTo(Defense25DArtifactDropStatus.Claimed));
|
|
Assert.That(world.Loadout.OwnedArtifactCount, Is.EqualTo(1));
|
|
Assert.That(drops.TryChoose(1, generation, token), Is.False);
|
|
Assert.That(drops.TryInteract(0), Is.False);
|
|
}
|
|
|
|
[Test]
|
|
public void SeededDropPositionsUseTheActualHomeAndStayOnClearRouteFreeGround()
|
|
{
|
|
Assert.That(Defense25DBattlefieldDefinition.CreateDefault().TryCreateLayout(
|
|
2718, out Defense25DRunLayout layout, out string error), Is.True, error);
|
|
Defense25DArtifactDrops first = new();
|
|
Defense25DArtifactDrops replay = new();
|
|
first.Bind(world, layout, settings, 9501);
|
|
replay.Bind(world, layout, settings, 9501);
|
|
|
|
Assert.That(first.DropStates.Count, Is.EqualTo(3));
|
|
for (int i = 0; i < first.DropStates.Count; i++)
|
|
{
|
|
Vector2 position = first.DropStates[i].Position;
|
|
Assert.That(position, Is.EqualTo(replay.DropStates[i].Position));
|
|
Assert.That(layout.Bounds.Contains(position), Is.True);
|
|
Assert.That(position.x - layout.Bounds.xMin, Is.GreaterThan(settings.DropFootprintRadius));
|
|
Assert.That(layout.Bounds.xMax - position.x, Is.GreaterThan(settings.DropFootprintRadius));
|
|
Assert.That(position.y - layout.Bounds.yMin, Is.GreaterThan(settings.DropFootprintRadius));
|
|
Assert.That(layout.Bounds.yMax - position.y, Is.GreaterThan(settings.DropFootprintRadius));
|
|
Assert.That(DistanceToRoutes(position, layout.Routes), Is.GreaterThan(.75f));
|
|
Assert.That(DistanceToPads(position, layout.PadPositions), Is.GreaterThan(1.25f));
|
|
Assert.That(DistanceToPads(position, layout.PortalPositions), Is.GreaterThan(1.25f));
|
|
|
|
float homeDistance = Vector2.Distance(position, layout.HomePosition);
|
|
if (i == 0)
|
|
Assert.That(homeDistance, Is.InRange(2f, 4f));
|
|
else
|
|
Assert.That(homeDistance, Is.InRange(18f, 26f));
|
|
}
|
|
|
|
Assert.That(Vector2.Distance(first.DropStates[1].Position, first.DropStates[2].Position),
|
|
Is.GreaterThanOrEqualTo(12f));
|
|
}
|
|
|
|
private static float DistanceToRoutes(Vector2 position, IReadOnlyList<IReadOnlyList<Vector2>> routes)
|
|
{
|
|
float nearest = float.PositiveInfinity;
|
|
foreach (IReadOnlyList<Vector2> route in routes)
|
|
for (int i = 0; i < route.Count - 1; i++)
|
|
nearest = Mathf.Min(nearest, DistanceToSegment(position, route[i], route[i + 1]));
|
|
return nearest;
|
|
}
|
|
|
|
private ScheduleRecord CaptureSchedule(Defense25DRunLayout layout, bool useOneSecondSteps)
|
|
{
|
|
Defense25DArtifactDrops drops = new();
|
|
drops.Bind(world, layout, settings, 7401);
|
|
ScheduleRecord record = new();
|
|
drops.DropSpawned += state => record.SpawnOrder.Add(state.DropId);
|
|
drops.ReliefWindowRequested += (id, start, duration) => record.Reliefs.Add((id, start, duration));
|
|
drops.NotifyPortalOpened(0, 0f);
|
|
drops.Step(59f);
|
|
if (useOneSecondSteps)
|
|
{
|
|
for (int second = 60; second <= 200; second++)
|
|
drops.Step(second);
|
|
}
|
|
else
|
|
{
|
|
drops.Step(200f);
|
|
}
|
|
|
|
return record;
|
|
}
|
|
|
|
private static float DistanceToPads(Vector2 position, IReadOnlyList<Vector2> pads)
|
|
{
|
|
float nearest = float.PositiveInfinity;
|
|
foreach (Vector2 pad in pads)
|
|
nearest = Mathf.Min(nearest, Vector2.Distance(position, pad));
|
|
return nearest;
|
|
}
|
|
|
|
private static float DistanceToSegment(Vector2 point, Vector2 start, Vector2 end)
|
|
{
|
|
Vector2 segment = end - start;
|
|
float t = Mathf.Clamp01(Vector2.Dot(point - start, segment) / segment.sqrMagnitude);
|
|
return Vector2.Distance(point, start + segment * t);
|
|
}
|
|
}
|
|
}
|