using System.Collections.Generic; using System.Linq; using BumpCombat.Defense; using BumpCombat.Defense.Comparison; using NUnit.Framework; namespace BumpCombat.PlayModeTests { public sealed class Defense25DPersonalMissionPlayModeTests { private readonly List openPortalIds = new(); private readonly List spawnedRequests = new(); private readonly List queuedRewardTokens = new(); private Defense25DPersonalMissions missions; private bool runCanAcceptMissions; private bool allowRewardQueue; private int nextSpawnId; [SetUp] public void SetUp() { openPortalIds.Clear(); spawnedRequests.Clear(); queuedRewardTokens.Clear(); runCanAcceptMissions = true; allowRewardQueue = true; nextSpawnId = 100; missions = new Defense25DPersonalMissions(); missions.Bind( new[] { 3, 6, 3 }, () => runCanAcceptMissions, () => openPortalIds, SpawnTargets, QueueReward); missions.Reset(7, 12345); } [Test] public void StartRequiresEnoughOpenPortalsAndRejectsConcurrentMission() { openPortalIds.AddRange(new[] { 2, 0, 2 }); Assert.That(missions.CanStart(1, out string reason), Is.False); Assert.That(reason, Does.Contain("3")); Assert.That(missions.TryStart(1), Is.False); Assert.That(spawnedRequests, Is.Empty); openPortalIds.AddRange(new[] { 1, 3 }); Assert.That(missions.TryStart(1), Is.True); Assert.That(spawnedRequests, Has.Count.EqualTo(3)); Assert.That(spawnedRequests.Select(request => request.PortalId).Distinct().Count(), Is.EqualTo(3)); Assert.That(spawnedRequests.All(request => request.RunGeneration == 7 && request.MissionId == 1 && request.MissionInstanceId == 1 && request.Source == Defense25DSpawnSource.PersonalMission), Is.True); Assert.That(missions.TryStart(2), Is.False); } [Test] public void MissionTargetsAreThreeSixAndThreeAndOnlyDeathsQueueOneReward() { for (int missionId = 1; missionId <= 3; missionId++) { openPortalIds.Clear(); for (int portalId = 0; portalId < 6; portalId++) openPortalIds.Add(portalId); int firstSpawnIndex = spawnedRequests.Count; Assert.That(missions.TryStart(missionId), Is.True); int targetCount = new[] { 3, 6, 3 }[missionId - 1]; IReadOnlyList targetIds = missions.States[missionId - 1].TargetSpawnIds; Assert.That(targetIds, Has.Count.EqualTo(targetCount)); for (int i = 0; i < targetIds.Count - 1; i++) { Assert.That(missions.ResolveTarget(7, targetIds[i], Defense25DSpawnSource.PersonalMission, missionId, missions.States[missionId - 1].MissionInstanceId, DefenseEnemyResolution.Death), Is.True); Assert.That(missions.States[missionId - 1].Status, Is.EqualTo(Defense25DPersonalMissionStatus.Active)); } Assert.That(missions.ResolveTarget(7, targetIds[targetIds.Count - 1], Defense25DSpawnSource.PersonalMission, missionId, missions.States[missionId - 1].MissionInstanceId, DefenseEnemyResolution.Death), Is.True); Assert.That(missions.States[missionId - 1].Status, Is.EqualTo(Defense25DPersonalMissionStatus.RewardPending)); Assert.That(missions.ResolveTarget(7, targetIds[0], DefenseEnemyResolution.Death), Is.False); Assert.That(queuedRewardTokens, Has.Count.EqualTo(missionId)); Assert.That(queuedRewardTokens[missionId - 1], Is.EqualTo($"personal-mission:7:{missionId}")); if (missionId < 3) Assert.That(missions.TryStart(missionId + 1), Is.False); Assert.That(missions.TryCompleteReward(7, missions.States[missionId - 1].MissionInstanceId), Is.True); Assert.That(missions.States[missionId - 1].Status, Is.EqualTo(Defense25DPersonalMissionStatus.Completed)); Assert.That(missions.TryCompleteReward(7, missions.States[missionId - 1].MissionInstanceId), Is.False); Assert.That(spawnedRequests.Skip(firstSpawnIndex).Take(targetCount) .Select(request => request.PortalId).Distinct().Count(), Is.EqualTo(targetCount)); } } [Test] public void RewardQueueFailureStaysPendingUntilQueuedAndChoiceIsConfirmed() { openPortalIds.AddRange(Enumerable.Range(0, 3)); Assert.That(missions.TryStart(1), Is.True); Defense25DPersonalMissionState mission = missions.States[0]; allowRewardQueue = false; foreach (int spawnId in mission.TargetSpawnIds) Assert.That(missions.ResolveTarget(7, spawnId, DefenseEnemyResolution.Death), Is.True); Assert.That(mission.Status, Is.EqualTo(Defense25DPersonalMissionStatus.RewardPending)); Assert.That(mission.RewardQueued, Is.False); Assert.That(queuedRewardTokens, Is.Empty); Assert.That(missions.TryCompleteReward(7, mission.MissionInstanceId), Is.False); allowRewardQueue = true; Assert.That(missions.TryQueuePendingReward(7, mission.MissionInstanceId), Is.True); Assert.That(queuedRewardTokens, Has.Count.EqualTo(1)); Assert.That(missions.TryQueuePendingReward(7, mission.MissionInstanceId), Is.False); Assert.That(mission.Status, Is.EqualTo(Defense25DPersonalMissionStatus.RewardPending)); Assert.That(missions.TryStart(2), Is.False); Assert.That(missions.TryCompleteReward(7, mission.MissionInstanceId), Is.True); Assert.That(mission.Status, Is.EqualTo(Defense25DPersonalMissionStatus.Completed)); } [Test] public void ArrivalFailsImmediatelyAndDoesNotResolveRemainingEnemiesOrReward() { openPortalIds.AddRange(Enumerable.Range(0, 3)); Assert.That(missions.TryStart(1), Is.True); IReadOnlyList targets = missions.States[0].TargetSpawnIds; Assert.That(missions.ResolveTarget(7, targets[0], DefenseEnemyResolution.Arrival), Is.True); Assert.That(missions.States[0].Status, Is.EqualTo(Defense25DPersonalMissionStatus.Failed)); Assert.That(missions.States[0].RemainingTargetCount, Is.EqualTo(2)); Assert.That(missions.ResolveTarget(7, targets[1], DefenseEnemyResolution.Death), Is.True); Assert.That(missions.States[0].RemainingTargetCount, Is.EqualTo(1)); Assert.That(queuedRewardTokens, Is.Empty); } [Test] public void UnownedGlobalEliteAndBossSummonIdsCannotCountAsMissionTargets() { openPortalIds.AddRange(Enumerable.Range(0, 3)); Assert.That(missions.TryStart(1), Is.True); Defense25DPersonalMissionState mission = missions.States[0]; int globalEliteId = mission.TargetSpawnIds[0]; int bossSummonId = mission.TargetSpawnIds[1]; Assert.That(missions.ResolveTarget(7, globalEliteId, Defense25DSpawnSource.GlobalElite, 1, mission.MissionInstanceId, DefenseEnemyResolution.Death), Is.False); Assert.That(missions.ResolveTarget(7, bossSummonId, Defense25DSpawnSource.BossSummon, 1, mission.MissionInstanceId, DefenseEnemyResolution.Death), Is.False); Assert.That(missions.States[0].RemainingTargetCount, Is.EqualTo(3)); Assert.That(queuedRewardTokens, Is.Empty); Assert.That(spawnedRequests.All(request => request.Source == Defense25DSpawnSource.PersonalMission), Is.True); } [Test] public void ResetClearsMissionOwnershipAndRejectsStaleGenerationCallbacks() { openPortalIds.AddRange(Enumerable.Range(0, 3)); Assert.That(missions.TryStart(1), Is.True); int staleSpawnId = missions.States[0].TargetSpawnIds[0]; missions.Reset(8, 54321); Assert.That(missions.Generation, Is.EqualTo(8)); Assert.That(missions.States.All(state => state.Status == Defense25DPersonalMissionStatus.Available), Is.True); Assert.That(missions.ResolveTarget(7, staleSpawnId, DefenseEnemyResolution.Death), Is.False); Assert.That(missions.TryCompleteReward(7, 1), Is.False); Assert.That(queuedRewardTokens, Is.Empty); Assert.That(missions.TryStart(1), Is.True); Assert.That(missions.States[0].MissionInstanceId, Is.EqualTo(1)); Assert.That(missions.States[0].TargetSpawnIds[0], Is.Not.EqualTo(staleSpawnId)); } private bool SpawnTargets( IReadOnlyList requests, out IReadOnlyList spawnIds) { var ids = new List(requests.Count); foreach (Defense25DPersonalMissionSpawnRequest request in requests) { spawnedRequests.Add(request); ids.Add(nextSpawnId++); } spawnIds = ids; return true; } private bool QueueReward(string token) { if (!allowRewardQueue) return false; queuedRewardTokens.Add(token); return true; } } }