/// <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); } }
/// <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); } }
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(); } }