예제 #1
0
        /// <summary>
        /// ZapQuake routine for destroying AirDefenses
        /// </summary>
        /// <param name="targets">AirDefense targets to destroy</param>
        /// <param name="earthQuakeSpells">Extracted Earthquake deployelement</param>
        /// <param name="lightningSpells">Extracted Lightning deployelement</param>
        /// <param name="waitUntilSpellAnimationIsOver">Bool if we should wait until the spell effect animation is completely gone</param>
        /// <returns></returns>
        IEnumerable <int> ZapQuakeAirDefenses(List <AirDefense> targets, DeployElement earthQuakeSpells, DeployElement lightningSpells, bool waitUntilSpellAnimationIsOver = true)
        {
            // Find targets until we have no more targets available
            for (var i = 0; i < targets.Count; i++)
            {
                Log.Info($"[LavaLoon] Going to destroy Air Defense {i + 1}");

                // Drop one earthquake spell and two lightningspells each Air Defense
                Log.Info($"[LavaLoon] Deploying Earthquake and Lightning spells for AirDefense {i + 1}");
                foreach (var t in Deploy.AtPoint(earthQuakeSpells, targets[i].Location.GetCenter()))
                {
                    yield return(t);
                }

                foreach (var t in Deploy.AtPoint(lightningSpells, targets[i].Location.GetCenter(), 2))
                {
                    yield return(t);
                }
            }
            // This should be prevent a CheckForDestroyed rescan while the animation is still going on (For example: http://i.imgur.com/SDPU5EG.jpg )
            if (waitUntilSpellAnimationIsOver)
            {
                Log.Debug("[LavaLoon] Waiting for 10 seconds until the zapquake animation is completely gone");
                yield return(10 * 1000);
            }
        }
예제 #2
0
        /// <summary>
        /// Finds AirDefenses which can and should be ZapQuaked.
        /// The length of the list is dependent on the available spells (2 lightnings, 1 earthquake = 1 AirDefense target)
        /// </summary>
        /// <param name="earthQuakeSpells">The available earthquake spells</param>
        /// <param name="lightningSpells">The available lightning spells</param>
        /// <returns>A list of AirDefenses which can and should be ZapQuaked</returns>
        List <AirDefense> FindAirDefenseTargets(DeployElement earthQuakeSpells, DeployElement lightningSpells, List <VisualObject> visuals)
        {
            var lightningsToDestroyAirDefenses = (int)Math.Floor((double)lightningSpells.Count / 2);
            var destroyableAirDefenses         = Math.Min(lightningsToDestroyAirDefenses, earthQuakeSpells.Count);

            Log.Info($"[LavaLoon] We've got {lightningSpells.Count} Lightning Spells and {earthQuakeSpells.Count}, which is enough to destroy {destroyableAirDefenses} AirDefenses.");


            var allAirDefenses = AirDefense.Find();

            try
            {
                var targetsToFindCount = Math.Min(destroyableAirDefenses, allAirDefenses.Count());
                if (targetsToFindCount == 0)
                {
                    Log.Error("[LavaLoon] FindAirDefenseTargets has been called even though it shouldn't have been called!");
                    return(null);
                }

                // If we need to find 2 or more AirDefense targets we want to find the closest AirDefenses
                if (targetsToFindCount > 1)
                {
                    var airDefensesOrderedByDeployPointDistance = allAirDefenses.OrderByDescending(x => SmartAirDeployHelpers.DistanceSqToClosestDeploypoint(x.Location.GetCenter()));
                    // furthestAirDefense = The airdefense which is the furthest away from deployzone
                    var furthestAirDefense   = airDefensesOrderedByDeployPointDistance.First();
                    var remainingAirDefenses = airDefensesOrderedByDeployPointDistance.Skip(1).ToList();
                    var orderedList          = OrderByDistance(furthestAirDefense, remainingAirDefenses).Take(targetsToFindCount).ToList();

                    // Add visuals
                    var orderedListCenters = orderedList.Select(x => x.Location.GetCenter());
                    visuals.Add(new PointsObject("AirDefenseTargets", Color.FromArgb(200, Color.CornflowerBlue), orderedListCenters));

                    return(orderedList);
                }
                var targetList = allAirDefenses.Take(1).ToList();

                // Add visuals
                var targetListCenters = targetList.Select(x => x.Location.GetCenter());
                visuals.Add(new PointsObject("AirDefenseTargets", Color.FromArgb(200, Color.CornflowerBlue), targetListCenters));

                return(targetList);
            }
            catch (Exception ex)
            {
                Log.Error("[LavaLoon] Exception occured during 'ZapQuakeAirDefenses'. More information can be found inside of the debug log.");
                Log.Debug("[LavaLoon] Exception details: " + ex);
                return(null);
            }
        }
        /// <summary>
        /// Distribute Balloons with equal space to each other along all potentialDeployPoints
        /// </summary>
        /// <param name="balloon"></param>
        /// <param name="potentialDeployPoints"></param>
        /// <param name="visuals"></param>
        /// <returns></returns>
        PointFT[] CalculateBalloonDeployPoints(DeployElement balloon, PointFT[] potentialDeployPoints, List <VisualObject> visuals)
        {
            if (balloon == null)
            {
                return(null);
            }

            var potentialDeployPointsCount = potentialDeployPoints.Count();
            var stepSize            = potentialDeployPointsCount / balloon.Count + 1;  // +1 becuase we would otherwise get 11 balloonDeployPoints for 10 balloons
            var balloonDeployPoints = new List <PointFT>();

            for (var i = 0; i < potentialDeployPointsCount; i += stepSize)
            {
                balloonDeployPoints.Add(potentialDeployPoints.ElementAt(i));
            }

            visuals.Add(new PointsObject("BabyDragon DeployPoints", Color.FromArgb(120, Color.Black), balloonDeployPoints, 2));
            return(balloonDeployPoints.ToArray());
        }
        /// <summary>
        /// Distribute Babydragons with equal space to each other along all potentialDeployPoints and move them 1 tile along the axis
        /// </summary>
        /// <param name="babyDragon"></param>
        /// <param name="potentialDeployPoints"></param>
        /// <param name="visuals"></param>
        /// <returns>The PointFT[] with all BabyDragon deploypoints</returns>
        PointFT[] CalculateBabyDragonDeployPoints(DeployElement babyDragon, PointFT[] potentialDeployPoints, List <VisualObject> visuals)
        {
            if (babyDragon == null)
            {
                return(null);
            }

            var stepSize = potentialDeployPoints.Count() / babyDragon.Count + 1;
            var babyDragonDeployPoints = new List <PointFT>();

            for (var i = 0; i < potentialDeployPoints.Count(); i += stepSize)
            {
                babyDragonDeployPoints.Add(potentialDeployPoints.ElementAt(i));
            }

            babyDragonDeployPoints = babyDragonDeployPoints.Select(x => x.TransformPositionAlongAxis(2).Constrain()).ToList();

            visuals.Add(new PointsObject("BabyDragon DeployPoints", Color.FromArgb(200, Color.ForestGreen), babyDragonDeployPoints, 2));
            return(babyDragonDeployPoints.ToArray());
        }
        /// <summary>
        /// ZapQuakes (2 lightningspells + 1 Earthquake) one Target (AirDefense)
        /// </summary>
        /// <param name="target">Target to Zapquake</param>
        /// <param name="earthQuakeSpell">Earthquake deployelement</param>
        /// <param name="lightningSpell">Lightningspell deployelement</param>
        /// <returns></returns>
        IEnumerable <int> ZapQuakeTarget(PointFT target, DeployElement earthQuakeSpell, DeployElement lightningSpell)
        {
            if (earthQuakeSpell?.Count < 1 || lightningSpell?.Count < 2)
            {
                Log.Debug("[BabyLoon] Not enough spells for ZapQuake available");
                yield break;
            }

            // Drop one earthquake spell and two lightningspells onto target
            Log.Info($"[BabyLoon] Deploying Earthquake and Lightning spells");
            foreach (var t in Deploy.AtPoint(earthQuakeSpell, target))
            {
                yield return(t);
            }

            foreach (var t in Deploy.AtPoint(lightningSpell, target, 2))
            {
                yield return(t);
            }
        }
        /// <summary>
        /// Filters the List of Deploy Elements, and returns the single type of unit that has the largest count currently.
        /// So if the Deployment element list has Barbarians (Count 20) and Goblins (Count 10) it will return an array with only the Barbs(Count20).
        /// This mimics a human where he would look to see which type of units he has the most of, and then drop those on collectors first...
        /// </summary>
        /// <param name="deployElements">List of multiple element types. (eg Ground units goblins, barbs), or Ranged units (Archers, Minions)</param>
        /// <returns>Array with the single type of Deploy Element that has the most unit count.</returns>
        public static DeployElement[] FilterTypesByCount(this IEnumerable <DeployElement> deployElements)
        {
            List <DeployElement> filteredList = new List <DeployElement>();

            int           unitCount = -1;
            DeployElement most      = null;

            foreach (var item in deployElements)
            {
                if (item.Count > unitCount)
                {
                    unitCount = item.Count;
                    most      = item;
                }
            }

            if (most != null)
            {
                filteredList.Add(most);
            }

            return(filteredList.ToArray());
        }
        PointFT[] CalculateRageDeployPoints(DeployElement rageSpell, PointFT[] potentialDeployPoints, List <VisualObject> visuals)
        {
            if (rageSpell == null)
            {
                return(null);
            }

            // PotentialdeployPoints[] is already ordered by angle ascending
            var minAnglePoint = potentialDeployPoints.First();
            var maxAnglePoint = potentialDeployPoints.Last();
            var line          = new Line(minAnglePoint, maxAnglePoint);

            visuals.Add(new LinesObject("RageSpellDeployPointLine", Color.FromArgb(200, Color.White), new[] { line }));

            // rageSpell.Count +1 because chopLine would start on the very first point of the line otherwise
            var spellDeployPoints = SmartAirDeployHelpers.ChopLine(line.Start, line.End, rageSpell.Count + 1);
            //visuals.Add(new PointsObject("BabyDragon DeployPoints", Color.FromArgb(250, Color.Tomato), spellDeployPoints, 3));
            var rageSpellDeployPoints = spellDeployPoints.Skip(1).Select(x => new PointFT((int)x.X, (int)x.Y)).ToArray();

            visuals.Add(new PointsObject("BabyDragon DeployPoints", Color.FromArgb(140, Color.Purple), rageSpellDeployPoints, 10));

            return(rageSpellDeployPoints);
        }
        /// <summary>
        /// Checks to see if the item is null, and if it has any troops left to deploy, if so, it adds it to the List.
        /// </summary>
        /// <param name="remainingElements">List to add the items to</param>
        /// <param name="item">Deployment Element to add if it is not used up.</param>
        public static void RecountAndAddIfAny(this List <DeployElement> remainingElements, DeployElement item)
        {
            if (remainingElements == null)
            {
                remainingElements = new List <DeployElement>();
            }

            if (item == null)
            {
                return;
            }

            item.Recount();

            if (item.Count > 0)
            {
                remainingElements.Add(item);
            }
        }
        public override IEnumerable <int> AttackRoutine()
        {
            var DE = DarkElixirStorage.Find()?.FirstOrDefault()?.Location.GetCenter();

            //set the target
            if (DE == null)
            {
                for (var i = 1; i <= 3; i++)
                {
                    Log.Warning($"bot didn't found the DE Storage .. we will attemp search NO. {i + 1}");
                    yield return(1000);

                    DE = DarkElixirStorage.Find()?.FirstOrDefault()?.Location.GetCenter();
                    if (DE != null)
                    {
                        Log.Warning($"DE Storage found after {i + 1} retries");
                        break;
                    }
                }
            }

            if (DE != null)
            {
                _target = (PointFT)DE;
            }
            else
            {
                Log.Debug("[Goblin Knife] coundn't locate the target after aligning the base");
                Log.Error("Couldn't find DE Storage we will return home");
                Surrender();

                yield break;
            }
            CreateDeployPoints();
            Log.Info($"[Goblin Knife] V{Version} Deploy start");

            //get troops
            var deployElements = Deploy.GetTroops();

            var clanCastle = deployElements.ExtractOne(u => u.ElementType == DeployElementType.ClanTroops && UserSettings.UseClanTroops);

            var earthQuakeSpell = deployElements.Extract(u => u.Id == DeployId.Earthquake);
            var jumpSpell       = deployElements.ExtractOne(DeployId.Jump);
            var giant           = deployElements.ExtractOne(DeployId.Giant);
            var goblin          = deployElements.ExtractOne(DeployId.Goblin);
            var wizard          = deployElements.ExtractOne(DeployId.Wizard);
            var barbarian       = deployElements.ExtractOne(DeployId.Barbarian);
            var archer          = deployElements.ExtractOne(DeployId.Archer);
            var wallbreaker     = deployElements.ExtractOne(DeployId.WallBreaker);
            var ragespell       = deployElements.ExtractOne(DeployId.Rage);
            var healspell       = deployElements.ExtractOne(DeployId.Heal);

            _freezeSpell = deployElements.ExtractOne(DeployId.Freeze);

            var heroes = deployElements
                         .Extract(u => (UserSettings.UseKing && u.ElementType == DeployElementType.HeroKing) ||
                                  (UserSettings.UseQueen && u.ElementType == DeployElementType.HeroQueen) ||
                                  (UserSettings.UseWarden && u.ElementType == DeployElementType.HeroWarden))
                         .ToList();

            //open near to dark elixer with 4 earthquakes
            if (earthQuakeSpell?.Sum(u => u.Count) >= 4)
            {
                foreach (var unit in earthQuakeSpell)
                {
                    foreach (int t in Deploy.AtPoint(unit, _earthQuakePoint, unit.Count))
                    {
                        yield return(t);
                    }
                }
            }
            else
            {
                _useJump = true;
            }
            yield return(1000);

            if (giant?.Count > 0)
            {
                foreach (int t in Deploy.AlongLine(giant, _attackLine.Item1, _attackLine.Item2, 6, 6))
                {
                    yield return(t);
                }
            }

            yield return(1000);

            if (wizard?.Count > 0)
            {
                foreach (int t in Deploy.AlongLine(wizard, _attackLine.Item1, _attackLine.Item2, 8, 4))
                {
                    yield return(t);
                }
            }

            if (barbarian?.Count > 0)
            {
                while (barbarian.Count > 0)
                {
                    int count = barbarian.Count;

                    Log.Info($"[Goblin Knife] Deploying {barbarian.PrettyName}");
                    foreach (int t in Deploy.AlongLine(barbarian, _attackLine.Item1, _attackLine.Item2, count, 4))
                    {
                        yield return(t);
                    }

                    // prevent infinite loop if deploy point is on red
                    if (barbarian.Count != count)
                    {
                        continue;
                    }

                    Log.Warning($"[Goblin Knife] Couldn't deploy {barbarian.PrettyName}");
                    break;
                }
            }

            if (archer?.Count > 0)
            {
                int archerCount = (int)(archer.Count / 2);
                Log.Info($"[Goblin Knife] Deploying {archer.PrettyName} ");
                foreach (int t in Deploy.AlongLine(archer, _attackLine.Item1, _attackLine.Item2, archerCount, 4))
                {
                    yield return(t);
                }
            }

            yield return(3000);

            if (ragespell?.Count >= 2)
            {
                foreach (int t in Deploy.AtPoint(ragespell, _ragePoint))
                {
                    yield return(t);
                }
            }
            if (wallbreaker?.Count > 0)
            {
                Log.Info($"[Goblin Knife] send test {wallbreaker.PrettyName} to check for bombs");
                foreach (int t in Deploy.AtPoint(wallbreaker, _orgin, 1))
                {
                    yield return(t);
                }
            }

            yield return(1000);

            while (wallbreaker?.Count > 0)
            {
                int count = wallbreaker.Count;
                Log.Info("[Goblin Knife] send wall breakers in groups");
                foreach (int t in Deploy.AtPoint(wallbreaker, _orgin, 3))
                {
                    yield return(t);
                }

                // prevent infinite loop if deploy point is on red
                if (wallbreaker.Count != count)
                {
                    continue;
                }

                Log.Warning($"[Goblin Knife] Couldn't deploy {wallbreaker.PrettyName}");
                break;
            }

            while (giant?.Count > 0)
            {
                int count = giant.Count;

                Log.Info($"[Goblin Knife] Deploying {giant.PrettyName} x{count}");
                foreach (int t in Deploy.AtPoint(giant, _orgin, count))
                {
                    yield return(t);
                }

                // prevent infinite loop if deploy point is on red
                if (giant.Count != count)
                {
                    continue;
                }

                Log.Warning($"[Goblin Knife] Couldn't deploy {giant.PrettyName}");
                break;
            }

            yield return(1000);

            while (wizard?.Count > 0)
            {
                int count = wizard.Count;

                Log.Info($"[Goblin Knife] Deploying {wizard}");
                foreach (int t in Deploy.AlongLine(wizard, _attackLine.Item1, _attackLine.Item2, 4, 2))
                {
                    yield return(t);
                }

                // prevent infinite loop if deploy point is on red
                if (wizard.Count != count)
                {
                    continue;
                }

                Log.Warning($"[Goblin Knife] Couldn't deploy {wizard.PrettyName}");
                break;
            }
            if (archer?.Count > 0)
            {
                Log.Info($"[Goblin Knife] Deploying {archer.PrettyName} ");
                foreach (int t in Deploy.AlongLine(archer, _attackLine.Item1, _attackLine.Item2, archer.Count, 4))
                {
                    yield return(t);
                }
            }

            yield return(1500);

            if (_useJump == true && jumpSpell?.Count > 0)
            {
                foreach (int t in Deploy.AtPoint(jumpSpell, _jumpPoint))
                {
                    yield return(t);
                }
            }

            if (healspell?.Count > 0)
            {
                foreach (int t in Deploy.AtPoint(healspell, _healPoint))
                {
                    yield return(t);
                }
            }

            if (clanCastle?.Count > 0)
            {
                Log.Info($"[Goblin Knife] Deploying {clanCastle.PrettyName}");
                foreach (int t in Deploy.AtPoint(clanCastle, _orgin))
                {
                    yield return(t);
                }
            }

            if (heroes.Any())
            {
                _delay = 1000;
                foreach (DeployElement hero in heroes.Where(u => u.Count > 0))
                {
                    foreach (int t in Deploy.AtPoint(hero, _orgin))
                    {
                        yield return(t);
                    }
                }
                watchHeroes = true;
            }
            yield return(_delay);

            while (goblin?.Count > 0)
            {
                int testGoblins = (int)(goblin.Count / 3);
                Log.Info($"[Goblin Knife] Deploying {goblin.PrettyName} x{testGoblins}");

                foreach (int t in Deploy.AtPoint(goblin, _orgin, testGoblins))
                {
                    yield return(t);
                }

                yield return(2000);

                int count = goblin.Count;

                Log.Info($"[Goblin Knife] Deploying {goblin.PrettyName} x{count}");
                foreach (int t in Deploy.AtPoint(goblin, _orgin, count))
                {
                    yield return(t);
                }

                // prevent infinite loop if deploy point is on red
                if (goblin.Count != count)
                {
                    continue;
                }

                Log.Warning($"[Goblin Knife] Couldn't deploy {goblin.PrettyName}");
                break;
            }
            //use freeze if inferno is found
            if (_freezeSpell?.Count > 0)
            {
                var infernos = InfernoTower.Find();
                // find and watch inferno towers
                if (infernos != null)
                {
                    foreach (var inferno in infernos)
                    {
                        inferno.FirstActivated += DropFreeze;

                        inferno.StartWatching();
                    }
                }
            }
            yield return(200);

            foreach (int t in Deploy.AtPoint(healspell, _target))
            {
                yield return(t);
            }

            foreach (int t in Deploy.AtPoint(ragespell, _target))
            {
                yield return(t);
            }
            if (watchHeroes == true)
            {
                Deploy.WatchHeroes(heroes, 7000);
            }
        }
예제 #10
0
        List <PointFT> CreateBalloonDeployPoints(DeployCalculationInformation deployInfo, DeployElement balloon, List <VisualObject> visuals)
        {
            var intersectionPointFt = deployInfo.IntersectionPointFT;
            var redlinePoints       = GameGrid.RedPoints.Where(x => x.DistanceSq(intersectionPointFt) < 460).ToList();

            redlinePoints = redlinePoints.OrderBy(x => x.Angle).ToList();

            var deployCount   = balloon.Count;
            var unitsPerStack = 3;
            var stackCount    = deployCount / unitsPerStack;          // We want to deploy balloons in 3 unit stacks

            Log.Debug($"[LavaLoon] We need to find {stackCount} deploypoints");

            var stepSize                = redlinePoints.Count / stackCount;
            var stackDeployPoints       = redlinePoints.Where((x, i) => i % stepSize == 0).ToList();
            var stackDeployPointsVisual = new PointsObject("balloons", Color.FromArgb(200, Color.NavajoWhite), stackDeployPoints, 1);

            visuals.Add(stackDeployPointsVisual);
            return(stackDeployPoints);
        }
        public override IEnumerable <int> AttackRoutine()
        {
            Log.Info("[Breakthrough] Deploy start");

            var funnelIds  = new[] { DeployId.Archer, DeployId.Barbarian, DeployId.Minion, DeployId.Wizard };
            var byLineIds  = new[] { DeployId.Archer, DeployId.Barbarian, DeployId.Minion, DeployId.Wizard, DeployId.Balloon, DeployId.Dragon, DeployId.BabyDragon, DeployId.Miner };
            var byPointIds = new[] { DeployId.Valkyrie, DeployId.Pekka, DeployId.Witch, DeployId.Goblin, DeployId.Bowler };

            // get a list of all deployable units
            var deployElements = Deploy.GetTroops();

            // extract spells into their own list
            var spells = deployElements.Extract(DeployElementType.Spell);

            // extract heores into their own list
            var heroes = deployElements.Extract(u => u.IsHero);

            // extract clanCastle into its own list
            var clanCastle = deployElements.ExtractOne(u => u.ElementType == DeployElementType.ClanTroops);

            // get tanks
            var tanks = deployElements.Extract(AttackType.Tank).ToArray();

            // get wallbreakers
            var wallBreakers = deployElements.ExtractOne(DeployId.WallBreaker);

            // get healers
            var healers = deployElements.ExtractOne(DeployId.Healer);

            // get funnel troops
            var funnel = funnelIds.Select(id => deployElements.FirstOrDefault(u => u.Id == id)).Where(u => u != null).ToArray();

            // get deploy all in a line
            var byLine = deployElements.Extract(byLineIds).ToArray();

            // get deploy all by point
            var byPoint = deployElements.Extract(byPointIds).ToArray();

            // get hogs
            var hogs = deployElements.ExtractOne(u => u.Id == DeployId.HogRider);

            // get heal spells
            var healSpells = spells.ExtractOne(u => u.Id == DeployId.Heal);

            // get rage spells
            var rageSpells = spells.ExtractOne(u => u.Id == DeployId.Rage);

            // user's wave delay setting
            var waveDelay = (int)(UserSettings.WaveDelay * 1000);

            // check if queen walk is an option
            if (heroes.Any(u => u.Id == DeployId.Queen) && healers?.Count >= 4)
            {
                var queen = heroes.ExtractOne(u => u.Id == DeployId.Queen);

                // get deploy points with queen walk
                CreateDeployPoints(true);

                // deploy queen walk
                Log.Info("[Breakthrough] Queen walk available.");
                Log.Info($"[Breakthrough] Deploying {queen.PrettyName}");
                foreach (var t in Deploy.AtPoint(queen, _qwPoint, waveDelay: waveDelay))
                {
                    yield return(t);
                }

                var healerCount = Math.Min(healers.Count, 4);
                Log.Info($"[Breakthrough] Deploying {healers.PrettyName} x{healerCount}");
                foreach (var t in Deploy.AtPoint(healers, _healerPoint, healerCount, waveDelay: waveDelay))
                {
                    yield return(t);
                }

                // watch queen
                Deploy.WatchHeroes(new List <DeployElement> {
                    queen
                });

                if (rageSpells?.Count > 1)
                {
                    Log.Info($"[Breakthrough] Deploying {rageSpells.PrettyName} x1");
                    foreach (var t in Deploy.AtPoint(rageSpells, _queenRagePoint, waveDelay: waveDelay))
                    {
                        yield return(t);
                    }
                }

                // wait 15 seconds
                yield return(15000);
            }
            else
            {
                // get deploy points without queen walk
                CreateDeployPoints(false);
            }

            var funnelTank = tanks.FirstOrDefault(u => u.Id == DeployId.Giant) ?? tanks.FirstOrDefault();

            // deploy four tanks if available
            if (funnelTank != null)
            {
                var deployCount = Math.Min(funnelTank.Count, 4);

                Log.Info($"[Breakthrough] Deploying {funnelTank.PrettyName} x{deployCount}");
                foreach (var t in Deploy.AlongLine(funnelTank, _attackLine.Item1, _attackLine.Item2, deployCount,
                                                   deployCount, waveDelay: waveDelay))
                {
                    yield return(t);
                }
            }

            // deploy funnel
            foreach (var unit in funnel.Where(u => u.Count > 0))
            {
                var deployElementCount = Math.Min(unit.Count, UserSettings.WaveSize / unit.UnitData.HousingSpace);

                Log.Info($"[Breakthrough] Deploying {unit.PrettyName} x{deployElementCount}");
                foreach (
                    var t in
                    Deploy.AlongLine(unit, _attackLine.Item1, _attackLine.Item2, deployElementCount, 4,
                                     waveDelay: waveDelay))
                {
                    yield return(t);
                }
            }

            // deploy Wallbreakers
            while (wallBreakers?.Count > 0)
            {
                var count = wallBreakers.Count;

                Log.Info($"[Breakthrough] Deploying {wallBreakers.PrettyName} x3");
                foreach (var t in Deploy.AtPoint(wallBreakers, _orgin, 3))
                {
                    yield return(t);
                }

                // prevent infinite loop if deploy point is on red
                if (wallBreakers.Count != count)
                {
                    continue;
                }

                Log.Warning($"[Breakthrough] Couldn't deploy {wallBreakers.PrettyName}");
                break;
            }


            // deploy the rest of the tanks
            while (tanks.Any(u => u.Count > 0))
            {
                var deployError = false;

                foreach (var unit in tanks.Where(u => u.Count > 0))
                {
                    var count = unit.Count;

                    Log.Info($"[Breakthrough] Deploying {unit.PrettyName} x{unit.Count}");
                    foreach (var t in Deploy.AtPoint(unit, _orgin, unit.Count, waveDelay: waveDelay))
                    {
                        yield return(t);
                    }

                    // prevent infinite loop if deploy point is on red
                    if (unit.Count != count)
                    {
                        continue;
                    }

                    Log.Warning($"[Breakthrough] Couldn't deploy {unit.PrettyName}");
                    deployError = true;
                    break;
                }
                if (deployError)
                {
                    break;
                }
            }

            if (rageSpells?.Count > 0)
            {
                Log.Info($"[Breakthrough] Deploying {rageSpells.PrettyName} x1");
                foreach (var t in Deploy.AtPoint(rageSpells, _ragePoint, waveDelay: waveDelay))
                {
                    yield return(t);
                }
            }

            if (healSpells?.Count > 0)
            {
                Log.Info($"[Breakthrough] Deploying {healSpells.PrettyName} x1");
                foreach (var t in Deploy.AtPoint(healSpells, _healPoint, waveDelay: waveDelay))
                {
                    yield return(t);
                }
            }

            while (byLine.Any(u => u.Count > 0))
            {
                foreach (var unit in byLine.Where(u => u.Count > 0))
                {
                    Log.Info($"[Breakthrough] Deploying {unit.PrettyName} x{unit.Count}");
                    foreach (
                        var t in
                        Deploy.AlongLine(unit, _attackLine.Item1, _attackLine.Item2, unit.Count, 4,
                                         waveDelay: waveDelay))
                    {
                        yield return(t);
                    }
                }
            }

            while (byPoint.Any(u => u.Count > 0))
            {
                var deployError = false;

                foreach (var unit in byPoint.Where(u => u.Count > 0))
                {
                    var count = unit.Count;

                    Log.Info($"[Breakthrough] Deploying {unit.PrettyName} x{unit.Count}");
                    foreach (var t in Deploy.AtPoint(unit, _orgin, unit.Count, waveDelay: waveDelay))
                    {
                        yield return(t);
                    }

                    // prevent infinite loop if deploy point is on red
                    if (unit.Count != count)
                    {
                        continue;
                    }

                    Log.Warning($"[Breakthrough] Couldn't deploy {unit.PrettyName}");
                    deployError = true;
                    break;
                }
                if (deployError)
                {
                    break;
                }
            }

            if (clanCastle?.Count > 0)
            {
                Log.Info($"[Breakthrough] Deploying {clanCastle.PrettyName}");
                foreach (var t in Deploy.AtPoint(clanCastle, _orgin, waveDelay: waveDelay))
                {
                    yield return(t);
                }
            }

            if (heroes.Any())
            {
                foreach (var hero in heroes.Where(u => u.Count > 0))
                {
                    Log.Info($"[Breakthrough] Deploying {hero.PrettyName}");
                    foreach (var t in Deploy.AtPoint(hero, _orgin, waveDelay: waveDelay))
                    {
                        yield return(t);
                    }
                }
            }

            if (healers?.Count > 0)
            {
                Log.Info($"[Breakthrough] Deploying {healers.PrettyName} x{healers.Count}");
                foreach (var t in Deploy.AtPoint(healers, _healerPoint, healers.Count, waveDelay: waveDelay))
                {
                    yield return(t);
                }
            }

            if (hogs?.Count > 0)
            {
                Log.Info($"[Breakthrough] Deploying {hogs.PrettyName} x{hogs.Count}");
                foreach (var t in Deploy.AtPoint(hogs, _orgin, hogs.Count, waveDelay: waveDelay))
                {
                    yield return(t);
                }
            }

            Deploy.WatchHeroes(heroes);

            // get freeze spells
            _freezeSpell = spells.ExtractOne(u => u.Id == DeployId.Freeze);

            // no freeze spells so end deployment
            if (!(_freezeSpell?.Count > 0))
            {
                yield break;
            }

            // find and watch inferno towers
            var infernos = InfernoTower.Find();

            foreach (var inferno in infernos)
            {
                inferno.FirstActivated += DropFreeze;

                inferno.StartWatching();
            }
        }