public void CombatShouldWorkWithTwoOpponentsAndSequentialTurns()
    {
        Globals.Config.ConcurrentTurnsEnabled = false;
        SetupScenarioForTwoZombies(out var zombie1, out var zombie2);

        // The zombies should not perform simultaneously
        GameSystems.D20.Actions.isSimultPerformer(zombie1).Should().BeFalse();
        GameSystems.D20.Actions.isSimultPerformer(zombie2).Should().BeFalse();

        // It should be zombie1's turn, run the game until it's zombie2's turn and check what zombie1 did
        GameSystems.D20.Initiative.CurrentActor.Should().Be(zombie1);
        Game.RunUntil(() => GameSystems.D20.Initiative.CurrentActor == zombie2, 10000);
        CompletedActions.Should()
        .HaveCount(1)
        .And.Contain(action => action.d20APerformer == zombie1 &&
                     action.d20ATarget == _player &&
                     action.d20ActType == D20ActionType.MOVE);
        ActionLog.Clear();

        // Now it's zombie2's turn, run until it's the player's turn and check what zombie2 did
        Game.RunUntil(() => GameSystems.D20.Initiative.CurrentActor == _player, 10000);
        CompletedActions.Should()
        .HaveCount(1)
        .And.Contain(action => action.d20APerformer == zombie2 &&
                     action.d20ATarget == _player &&
                     action.d20ActType == D20ActionType.MOVE);

        // Since both zombies just moved, there should be no combat entries
        CombatLog.Should().BeEmpty();
    }
    public void AITurnMaxActionsShouldBeEnforced()
    {
        Globals.Config.ConcurrentTurnsEnabled = false;
        Globals.Config.AITurnMaxActions       = 3;
        SetupScenarioForTwoZombies(out var zombie1, out var zombie2);

        // It should be zombie1's turn.
        GameSystems.D20.Initiative.CurrentActor.Should().Be(zombie1);
        List <D20Action> GetZombie1Actions() => CompletedActions.Where(a => a.d20APerformer == zombie1).ToList();

        GetZombie1Actions().Should().HaveCount(0);

        // Ensure the zombie has infinite movement, essentially
        GameSystems.D20.Actions.CurrentSequence.tbStatus.surplusMoveDistance = 100;

        // Complete the action 3 times total
        GameSystems.D20.Actions.PerformOnAnimComplete(zombie1, GameSystems.D20.Actions.CurrentAction.animID);
        GetZombie1Actions().Should().HaveCount(1);
        GameSystems.D20.Initiative.CurrentActor.Should().Be(zombie1);

        GameSystems.D20.Actions.PerformOnAnimComplete(zombie1, GameSystems.D20.Actions.CurrentAction.animID);
        GetZombie1Actions().Should().HaveCount(2);
        GameSystems.D20.Initiative.CurrentActor.Should().Be(zombie1);

        GameSystems.D20.Actions.PerformOnAnimComplete(zombie1, GameSystems.D20.Actions.CurrentAction.animID);
        GetZombie1Actions().Should().HaveCount(3);
        GameSystems.D20.Initiative.CurrentActor.Should().Be(zombie2);
    }
    public void ActionsArePerformedAfterMovingIntoRange()
    {
        // Spawn a player and a chest (at a distance), and click on the chest
        var player      = CreatePlayer();
        var lockedChest = Create(TestProtos.LockedChest);

        ClickOn(lockedChest);

        // This should make the party leader perform the default action,
        // which is to open the container.
        Game.RunUntil(() => GameSystems.D20.Actions.IsCurrentlyPerforming(player));

        var sequence = GameSystems.D20.Actions.CurrentSequence ?? throw new NullReferenceException();

        sequence.performer.Should().Be(player);
        sequence.targetObj.Should().Be(lockedChest);

        // Run until the sequence has concluded
        Game.RunUntil(() => !GameSystems.D20.Actions.IsCurrentlyPerforming(player), 5000);

        // Since the player was not in reach, there should be a move action performed,
        // then the opening action
        CompletedActions.Should().SatisfyRespectively(
            first =>
        {
            first.d20APerformer.Should().Be(player);
            first.d20ActType.Should().Be(D20ActionType.MOVE);
            // The target location should bring the player into reach of the container
            first.destLoc.DistanceTo(lockedChest.GetLocationFull()).Should().BeLessOrEqualTo(
                player.GetRadius()
                + lockedChest.GetRadius()
                // Reach returns feet
                + player.GetReach(D20ActionType.OPEN_CONTAINER) * locXY.INCH_PER_FEET
                );
        },
            second =>
        {
            second.d20ActType.Should().Be(D20ActionType.OPEN_CONTAINER);
            second.d20APerformer.Should().Be(player);
            second.d20ATarget.Should().Be(lockedChest);
        }
            );
    }