// Air deploy methods public static IEnumerable <int> ZapAirDefense() { // Todo: use 3 lighting if th7. var airDefenses = AirDefense.Find(CacheBehavior.ForceScan); var targetAirDefense = airDefenses .OrderBy(a => a.Location.GetCenter().DistanceSq(AllInOnePushDeploy.Origin)) .ElementAtOrDefault(2) ?? airDefenses .OrderBy(a => a.Location.GetCenter().DistanceSq(AllInOnePushDeploy.Origin)) .ElementAtOrDefault(1) ?? airDefenses .FirstOrDefault(); var zapPoint = targetAirDefense.Location.GetCenter(); if (eq?.Sum(u => u.Count) > 0) { foreach (var unit in eq) { foreach (var t in Deploy.AtPoint(unit, zapPoint)) { yield return(t); } break; } } foreach (var t in Deploy.AtPoint(lightingSpell, zapPoint, 2)) { yield return(t); } yield return(new Random().Next(1200, 2500)); }
/// <summary> /// Returns true if we are certain that we found all AirDefenses, otherwise false /// </summary> /// <param name="townHallLevel">The opponent's townhall level</param> /// <returns>True: We found all air defenses, False: We don't know if we found all Air Defenses</returns> internal static bool AllAirDefensesFound(int?townHallLevel) { var foundAirDefensesCount = AirDefense.Find().Count(); var maxAirDefensesCount = CountMaxPossibleAirDefenses(townHallLevel); if (foundAirDefensesCount >= maxAirDefensesCount) { return(true); } Logger.Info($"[Smart Air] Couldn't find all AirDefenses. Found {foundAirDefensesCount}/{maxAirDefensesCount} AirDefenses."); return(false); }
/// <summary> /// check Opppnent base to see if it's engineered base or not /// </summary> /// <returns>true if engineered</returns> public static bool IsEngineeredBase() { var defenses = ArcherTower.Find()?.Count(); defenses += WizardTower.Find()?.Count(); defenses += AirDefense.Find().Count(); if (defenses <= 3) { return(true); } return(false); }
/// <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> /// Get second closest AirDefense to deployPoints for ZapQuake /// </summary> /// <param name="closestThRedPoint"></param> /// <returns></returns> AirDefense FindZapQuakeTarget(PointFT closestThRedPoint, List <VisualObject> visuals) { var airDefenses = AirDefense.Find(); var secondClosestAirDefense = airDefenses.OrderBy(x => x.Location.GetCenter().DistanceSq(closestThRedPoint)).ElementAtOrDefault(1); // Draw crosshair to visualize ZapQuakeTarget if (secondClosestAirDefense == null) { return(airDefenses.FirstOrDefault()); } var zapQuakeTarget = new PointFT[] { secondClosestAirDefense.Location.GetCenter() }; visuals.Add(new CrosshairObject(zapQuakeTarget, Color.Maroon)); return(secondClosestAirDefense); }
IEnumerable <int> FindAirDefenses() { airDefenses = AirDefense.Find(); //After finding air Defenses, Sort them. if (airDefenses.Length > 1) { //Now that we found all Air Defenses, order them in the array with closest AD to Target first. Array.Sort(airDefenses, delegate(AirDefense ad1, AirDefense ad2) { return(HumanLikeAlgorithms.DistanceFromPoint(ad1, mainTarget.DeployGrunts) .CompareTo(HumanLikeAlgorithms.DistanceFromPoint(ad2, mainTarget.DeployGrunts))); }); } else { Log.Error($"{Tag} Somehow no air defenses were found in Attack Phase. - Surrender."); surrender = true; yield break; } }
public static bool IsAirDefenseExposed(int distance = 7) { var redPoints = GameGrid.RedPoints.Where( point => !(point.X > 18 && point.Y > 18 || point.X > 18 && point.Y < -18 || point.X < -18 && point.Y > 18 || point.X < -18 && point.Y < -18)); var ADs = AirDefense.Find().Where(c => c.Location.GetCenter() .DistanceSq(redPoints.OrderBy(p => p.DistanceSq(c.Location.GetCenter())) .FirstOrDefault()) <= distance); if (ADs.Count() > 2) { using (Bitmap bmp = Screenshot.Capture()) { var d = DateTime.UtcNow; Screenshot.Save(bmp, "Exposed Air Defense {d.Year}-{d.Month}-{d.Day} {d.Hour}-{d.Minute}-{d.Second}-{d.Millisecond}"); } Log.Warning("This base hase exposed air defenses, we will skip that base."); return(true); } return(false); }
public override double ShouldAccept() { if (!PassesBasicAcceptRequirements()) { return(0); } //TODO - Check which kind of army we have trained. Calculate an Air Offense Score, and Ground Offense Score. //TODO - Find all Base Defenses, and calculate an AIR and Ground Defensive Score. //TODO - From Collector/Storage fill levels, determine if loot is in Collectors, or Storages... (Will help to decide which alg to use.) //Verify that the Attacking Army contains at least 6 Dragons. deployElements = Deploy.GetTroops(); var dragons = deployElements.FirstOrDefault(u => u.Id == DeployId.Dragon); if (dragons == null || dragons?.Count < 6) { Log.Error($"{Tag} Army not correct! - Dark Dragon Deploy Requires at least 6 Dragons to function Properly. (You have {dragons?.Count ?? 0} dragons)"); return(0); } //Verify that there are enough spells to take out at least ONE air defense. var lightningSpells = deployElements.FirstOrDefault(u => u.ElementType == DeployElementType.Spell && u.Id == DeployId.Lightning); List <DeployElement> earthquakeSpells = deployElements.Where(u => u.ElementType == DeployElementType.Spell && u.Id == DeployId.Earthquake).ToList(); var lightningCount = lightningSpells?.Count ?? 0; var earthquakeCount = 0; //Get a count of all earthquake spells... donated, or brewed... foreach (var spell in earthquakeSpells.Where(s => s.Count > 0)) { earthquakeCount += spell.Count; } if (lightningCount < 2 || lightningCount < 3 && earthquakeCount < 1) { //We dont have the Spells to take out the Closest Air Defense... Surrender before we drop any Dragons! Log.Error($"{Tag} We don't have enough spells to take out at least 1 air defense... Lightning Spells:{lightningCount}, Earthquake Spells:{earthquakeCount}"); return(0); } if (deployElements.Count >= 11) { //Possibly Too Many Deployment Elements! Bot Doesnt Scroll - Change Army Composition to have less than 12 unit types! Log.Warning($"{Tag} Warning! Full Army! - The Bot does not scroll through choices when deploying units... If your army has more than 11 unit types, The bot will not see them all, and cannot deploy everything!)"); } //Write out all the unit pretty names we found... Log.Debug($"{Tag} Deployable Troops: {ToUnitString(deployElements)}"); Log.Info($"{Tag} Base meets minimum Requirements... Checking DE Storage/Air Defense Locations..."); //Check to see if we can find ANY air Defenses... (Could Skip here if not all are found.) var airDefensesTest = AirDefense.Find(); if (airDefensesTest.Length == 0) { Log.Warning($"{Tag} Could not find ANY air defenses - Skipping"); return(0); } //For now just log the Trophy counts... //TODO - Later we will want to use these to see if the base is worth attacking when we are in Trophy mode... try { int trophiesWin, trophiesDefeat = -1; if (Opponent.GetLootableTrophies(out trophiesWin, out trophiesDefeat)) { Log.Info($"Trophies if we Win: {trophiesWin}, Trophies if we lose: {trophiesDefeat}"); } } catch (Exception ex) { Log.Error($"Error getting trophy values... - {ex.Message} - {ex.StackTrace}"); } Log.Info($"{Tag} Found {airDefensesTest.Length} Air Defense Buildings.. Continuing Attack.."); //We are Good to attack! return(1); }
public override IEnumerable <int> AttackRoutine() { #if DEBUG debugMode = true; #endif var visuals = new List <VisualObject>(); // user's wave delay setting var waveDelay = (int)(UserSettings.WaveDelay * 1000); // Call it once in order to cache the RedPoints in the beginning // If this will be called after spell deployment the redline will disappear!! - Do not remove this! var redPoints = GameGrid.RedPoints; // get a list of all deployable units var deployElements = Deploy.GetTroops(); Log.Debug("[Debug] Deployable Troops: " + ToUnitString(deployElements)); if (!HasNeededTroops(deployElements)) { Log.Error("[LavaLoon] Couldn't find a known troop composition. Consider using one of the known troop compositions. Check our forums to learn more about " + "the LavaLoon Deploy in order to achieve the best possible results."); Surrender(); yield break; } // extract heores into their own list var heroes = deployElements .Extract(u => (UserSettings.UseKing && u.ElementType == DeployElementType.HeroKing) || (UserSettings.UseQueen && u.ElementType == DeployElementType.HeroQueen) || (UserSettings.UseWarden && u.ElementType == DeployElementType.HeroWarden)) .ToList(); // extract clanCastle into its own list var clanCastle = deployElements.ExtractOne(u => u.ElementType == DeployElementType.ClanTroops && UserSettings.UseClanTroops); // extract spells into their own list var lightningSpells = deployElements.ExtractOne(x => x.Id == DeployId.Lightning); var earthQuakeSpells = deployElements.ExtractOne(x => x.Id == DeployId.Earthquake); var rageSpells = deployElements.ExtractOne(x => x.Id == DeployId.Rage); var freezeSpells = deployElements.ExtractOne(x => x.Id == DeployId.Freeze); // extract tank units into their own list var tanks = deployElements.Extract(AttackType.Tank).ToArray(); // extract balloons into their own list var balloon = deployElements.ExtractOne(x => x.Id == DeployId.Balloon); // extract the attack units into their own list var damageDealer = deployElements.Extract(AttackType.Damage).OrderByDescending(x => x.UnitData.HP).ToArray(); // extract wallbreakers into their own list var wallBreakers = deployElements.ExtractOne(x => x.Id == DeployId.WallBreaker); #region ZapQuake AirDefenses if possible and rescan with a CheckForDestroyed // ZapQuake AirDefenses if required spells exist List <AirDefense> zapQuakeTargets = null; if (earthQuakeSpells?.Count > 0 && lightningSpells?.Count >= 2) { zapQuakeTargets = FindAirDefenseTargets(earthQuakeSpells, lightningSpells, visuals); if (zapQuakeTargets?.Count > 0) { foreach (var t in ZapQuakeAirDefenses(zapQuakeTargets, earthQuakeSpells, lightningSpells)) { yield return(t); } } else { Log.Warning("[LavaLoon] Couldn't find AirDefense targets for ZapQuaking!"); } } else { Log.Info("[LavaLoon] Could not find enough spells (at least 1 Earthquake and 2 Lightning spells) to zapquake AirDefenses"); } // If we have zapquaked something we should rescan to check for the remaining AirDefenses if (zapQuakeTargets != null) { Log.Info("[LavaLoon] Rescanning AirDefenses to check for remaining AirDefenses"); AirDefense.Find(CacheBehavior.CheckForDestroyed); } #endregion var airDefenses = AirDefense.Find(); var deployInfo = PrepareDeployCalculation(airDefenses, visuals); var tankDeployPoints = CreateLavaHoundDeployPoints(deployInfo, visuals).ToArray(); var balloonDeployPoints = CreateBalloonDeployPoints(deployInfo, balloon, visuals).ToArray(); var attackWaveDeployPoints = CreateAttackWaveDeployPoints(deployInfo, visuals).ToArray(); var funnelCreatorDeployPoints = CreateFunnelCreatorDeployPoints(deployInfo, visuals).ToArray(); var intersectionDeployPoint = CreateIntersectionDeployPoint(deployInfo, visuals).First(); // deploy all tanks if available if (tanks != null) { foreach (var tank in tanks) { var deployCount = tank.Count; Log.Info($"[Breakthrough] Deploying {tank.PrettyName} x{deployCount}"); // Deploy all Tanks alternating to each tank deploypoint (e. g.: left - right, left - right, left) while (tank?.Count > 0) { var initialTankCount = tank.Count; foreach (var deployPoint in tankDeployPoints) { foreach (var t in Deploy.AtPoint(tank, deployPoint, 1)) { yield return(t); } } // Prevent an infinite loop if deploy point is inside of the redzone if (tank.Count != initialTankCount) { continue; } Log.Warning($"[LavaLoon] Couldn't deploy {tank.PrettyName}"); break; } } } if (balloon != null) { Log.Info($"[LavaLoon] Deploying {balloon.PrettyName} x{balloon.Count}"); while (balloon?.Count > 0) { var initialWallBreakersCount = balloon?.Count; foreach (var t in Deploy.AtPoints(balloon, balloonDeployPoints, 3, 50)) { yield return(t); } // Prevent an infinite loop if deploy point is inside of the redzone if (balloon.Count != initialWallBreakersCount) { yield return(1200); continue; } Log.Warning($"[LavaLoon] Couldn't deploy {wallBreakers.PrettyName}"); break; } } if (clanCastle?.Count > 0) { Log.Info($"[LavaLoon] Deploying {clanCastle.PrettyName}"); foreach (var t in Deploy.AtPoint(clanCastle, intersectionDeployPoint, waveDelay: waveDelay)) { yield return(t); } } if (damageDealer.Any()) { Log.Debug("[LavaLoon] 1500ms Delay before deploying the attack wave"); yield return(1500); foreach (var troop in damageDealer) { Log.Info($"[LavaLoon] Deploying {troop.PrettyName} x{troop.Count}"); } while (damageDealer.Sum(x => x.Count) > 0) { var initialTroopCount = damageDealer.Sum(x => x.Count); foreach (var t in Deploy.AtPoints(damageDealer, funnelCreatorDeployPoints)) { yield return(t); } // Prevent an infinite loop if deploy point is inside of the redzone if (damageDealer.Sum(x => x.Count) != initialTroopCount) { continue; } var remainingTroops = damageDealer.Where(x => x.Count > 0).ToList(); foreach (var troop in remainingTroops) { Log.Warning($"[LavaLoon] Couldn't deploy x{troop.Count} {troop.PrettyName}"); } break; } } if (heroes.Any()) { Log.Debug("[LavaLoon] 1500ms Delay before deploying heroes"); yield return(1500); var heroDeployPoint = intersectionDeployPoint.TransformPositionAlongAxis(4).Constrain(); foreach (var hero in heroes.Where(u => u.Count > 0)) { Log.Info($"[LavaLoon] Deploying {hero.PrettyName}"); foreach (var t in Deploy.AtPoint(hero, heroDeployPoint)) { yield return(t); } } Deploy.WatchHeroes(heroes, 7000); } if (wallBreakers?.Count > 0) { var wallBreakersDeployPoint = intersectionDeployPoint.TransformPositionAlongAxis(4).Constrain(); Log.Info($"[LavaLoon] Deploying {wallBreakers.PrettyName} x{wallBreakers.Count}"); while (wallBreakers?.Count > 0) { var initialWallBreakersCount = wallBreakers?.Count; // Deploy Wallbreakers in 3 unit stacks (which is enough to crush walls) and deploy further stacks with 1.2s delay // in order to avoid that all of them get destroyed by splash damages foreach (var t in Deploy.AtPoint(wallBreakers, wallBreakersDeployPoint, 3, waveDelay: waveDelay)) { yield return(t); } // Prevent an infinite loop if deploy point is inside of the redzone if (wallBreakers.Count != initialWallBreakersCount) { yield return(1200); continue; } Log.Warning($"[LavaLoon] Couldn't deploy {wallBreakers.PrettyName}"); break; } } if (debugMode) { VisualizeDeployment(visuals); } }
public override double ShouldAccept() { if (!PassesBasicAcceptRequirements()) { return(0); } //TODO - Check which kind of army we have trained. Calculate an Air Offense Score, and Ground Offense Score. //TODO - Find all Base Defenses, and calculate an AIR and Ground Defensive Score. //TODO - From Collector/Storage fill levels, determine if loot is in Collectors, or Storages... (Will help to decide which alg to use.) //Verify that the Attacking Army contains at least 6 Dragons. deployElements = Deploy.GetTroops(); var dragons = deployElements.FirstOrDefault(u => u.Id == DeployId.Dragon); if (dragons == null || dragons?.Count < 6) { Log.Error($"{Tag} Army not correct! - Dark Dragon Deploy Requires at least 6 Dragons to function Properly. (You have {dragons?.Count ?? 0} dragons)"); return(0); } //Verify that there are enough spells to take out at least ONE air defense. var lightningSpells = deployElements.FirstOrDefault(u => u.ElementType == DeployElementType.Spell && u.Id == DeployId.Lightning); List <DeployElement> earthquakeSpells = deployElements.Where(u => u.ElementType == DeployElementType.Spell && u.Id == DeployId.Earthquake).ToList(); var lightningCount = lightningSpells?.Count ?? 0; var earthquakeCount = 0; //Get a count of all earthquake spells... donated, or brewed... foreach (var spell in earthquakeSpells.Where(s => s.Count > 0)) { earthquakeCount += spell.Count; } if (lightningCount < 2 || lightningCount < 3 && earthquakeCount < 1) { //We dont have the Spells to take out the Closest Air Defense... Surrender before we drop any Dragons! Log.Error($"{Tag} We don't have enough spells to take out at least 1 air defense... Lightning Spells:{lightningCount}, Earthquake Spells:{earthquakeCount}"); return(0); } if (deployElements.Count >= 11) { //Possibly Too Many Deployment Elements! Bot Doesnt Scroll - Change Army Composition to have less than 12 unit types! Log.Warning($"{Tag} Warning! Full Army! - The Bot does not scroll through choices when deploying units... If your army has more than 11 unit types, The bot will not see them all, and cannot deploy everything!)"); } //Write out all the unit pretty names we found... Log.Debug($"{Tag} Deployable Troops: {ToUnitString(deployElements)}"); Log.Info($"{Tag} Base meets minimum Requirements... Checking DE Storage/Air Defense Locations..."); //Grab the Locations of the DE Storage darkElixirStorage = HumanLikeAlgorithms.TargetDarkElixirStorage(); if (!darkElixirStorage.ValidTarget) { Log.Warning($"{Tag} No Dark Elixir Storage Found - Skipping"); return(0); } //Get the locaiton of all Air Defenses var airDefensesTest = AirDefense.Find(); if (airDefensesTest.Length == 0) { Log.Warning($"{Tag} Could not find ANY air defenses - Skipping"); return(0); } Log.Info($"{Tag} Found {airDefensesTest.Length} Air Defense Buildings.. Continuing Attack.."); if (airDefensesTest.Length > 1) { //Now that we found all Air Defenses, order them in the array with closest AD to Target first. Array.Sort(airDefensesTest, delegate(AirDefense ad1, AirDefense ad2) { return(HumanLikeAlgorithms.DistanceFromPoint(ad1, darkElixirStorage.DeployGrunts) .CompareTo(HumanLikeAlgorithms.DistanceFromPoint(ad2, darkElixirStorage.DeployGrunts))); }); } //Create the Funnel Points deFunnelPoints = darkElixirStorage.GetFunnelingPoints(30); balloonFunnelPoints = darkElixirStorage.GetFunnelingPoints(20); #if DEBUG //During Debug, Create an Image of the base including what we found. CreateDebugImages(); #endif //We are Good to attack! return(1); }
public override IEnumerable <int> AttackRoutine() { Log.Info($"{Tag} Deploy start - V.{Assembly.GetExecutingAssembly().GetName().Version.ToString()}"); // Prepare: // Find the airDefenses again. // We cannot use the airDefenses from ShouldAttack, because the bot wil move // the camera after accepting an opponent. // Then the cache gets invalidated // So we have to find the air defenses again airDefenses = AirDefense.Find(); //STEP 1 ******* Destroy all air defenses using Lightling & Quake if needed. ******* foreach (var t in DestroyAirDefenses()) { yield return(t); } //Pause after killing Air Defenses (to make it look like a person is attacking) yield return(Rand.Int(1000, 2000)); //STEP 2 ******* Deploy Dragon funnel and Main Dragon Force. ******* foreach (var t in DeployDragons()) { yield return(t); } //Pause yield return(Rand.Int(2000, 3000)); //STEP 3 ******* Deploy Lava Hounds (if any Exist). ******* foreach (var t in DeployLavaHounds()) { yield return(t); } //Pause for a little while... - Long enough for main dragons to begin to enter the base. yield return(Rand.Int(3000, 4000)); //STEP 4 ******* Deploy Ballons And/Or Hogs ******* foreach (var t in DeployBalloonsAndHogs()) { yield return(t); } //Pause a while... - Then drop the Heros. - They should start going through walls towards the center. yield return(Rand.Int(7000, 9000)); //STEP 5 ******* Deploy King (He tanks for wallbreakers a little) ******* foreach (var t in DeployKing()) { yield return(t); } //Wait for king to be targeted... yield return(Rand.Int(1000, 1200)); //STEP 6 ******* Deploy All Wallbreakers (Get the heros going inside the base) ******* foreach (var t in DeployWallBreakers()) { yield return(t); } //STEP 7 ******* Next Drop the Warden, (if we have one) ******* foreach (var t in DeployWarden()) { yield return(t); } //Pause a while... - Then drop the Queen, so she starts following the King into the base. yield return(Rand.Int(2000, 4000)); //STEP 8 ******* Drop the queen so she will follow the king in. ******* foreach (var t in DeployQueen()) { yield return(t); } //STEP 9 ******* Now that all heros have been deployed begin watching them and activate ability etc. ******* WatchHeros(); //TODO Deploy Baby Drags on the Back End - on Air D's 3 & 4' //STEP 10 ******* Deploy the Clan Castle if user settings say to ******* foreach (var t in DeployClanCastle()) { yield return(t); } //STEP 11 ******* If there is a Rage Spell, Deploy it now - Right in front of the DE Storage! ******* foreach (var t in DeployRageSpell()) { yield return(t); } //STEP 12 ******* Drop healers on the Heros if healers exist. ******* foreach (var t in DeployHealers()) { yield return(t); } //Pause for a little while longer... waiting for things to develop yield return(Rand.Int(4000, 6000)); //STEP 13 ******* Deploy Minions & Others? ******* foreach (var t in DeployOthers()) { yield return(t); } //TODO If there is a Heal Spell... Deploy it here... (This one will be harder to predict where to drop...) Meh, skipping for now. //STEP 14 ******* Use ANY other Spells at this point... (So they are ALL GONE!) ******* foreach (var t in DeployLeftoverSpells()) { yield return(t); } //STEP 15 ******* Deploy ANY troops left... (So they are ALL GONE!) ******* foreach (var t in DeployLeftoverTroops()) { yield return(t); } //At this point the attack is fully deployed... just waiting for the timer to run out, or base to be 100% destroyed. }
public override IEnumerable <int> AttackRoutine() { #if DEBUG debugMode = true; #endif var visuals = new List <VisualObject>(); // get a list of all deployable units var deployElements = Deploy.GetTroops(); Log.Debug("[Debug] Deployable Troops: " + ToUnitString(deployElements)); if (!HasNeededTroops(deployElements)) { Log.Error("[Smart Air] Couldn't find a known troop composition. Consider using one of the known troop compositions. Check our forums to learn more about " + "the Smart Air Deploy in order to achieve the best possible results."); Surrender(); yield break; } // 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); // extract spells into their own list var lightningSpells = deployElements.ExtractOne(x => x.Id == DeployId.Lightning); var earthQuakeSpells = deployElements.ExtractOne(x => x.Id == DeployId.Earthquake); var rageSpells = deployElements.ExtractOne(x => x.Id == DeployId.Rage); var freezeSpells = deployElements.ExtractOne(x => x.Id == DeployId.Freeze); #region ZapQuake AirDefenses if possible and rescan with a CheckForDestroyed // ZapQuake AirDefenses if required spells exist List <AirDefense> zapQuakeTargets = null; if (earthQuakeSpells?.Count > 0 && lightningSpells?.Count >= 2) { zapQuakeTargets = FindAirDefenseTargets(earthQuakeSpells, lightningSpells, visuals); if (!debugMode) { if (zapQuakeTargets?.Count > 0) { foreach (var t in ZapQuakeAirDefenses(zapQuakeTargets, earthQuakeSpells, lightningSpells)) { yield return(t); } } else { Log.Warning("[Smart Air] Couldn't find AirDefense targets for ZapQuaking!"); } } } else { Log.Info("[Smart Air] Could not find enough spells (at least 1 Earthquake and 2 Lightning spells) to zapquake AirDefenses"); } // If we have zapquaked something we should rescan to check for the remaining AirDefenses if (zapQuakeTargets != null) { Log.Info("[Smart Air] Rescanning AirDefenses to check for remaining AirDefenses"); AirDefense.Find(CacheBehavior.CheckForDestroyed); } #endregion var airDefenses = AirDefense.Find(); var lavaLoonDeployPoints = CreateLavaHoundDeployPoints(airDefenses, visuals); if (debugMode) { VisualizeDeployment(visuals); } }