Esempio n. 1
0
        /// <summary>
        /// Returns an ordered list of AirDefenses sorted by distance ascending
        /// </summary>
        /// <param name="start">Initial AirDefense, fixed point for finding the nearest AirDefenses</param>
        /// <param name="remainingAirDefenses">List of the remaining AirDefenses</param>
        /// <returns></returns>
        List <AirDefense> OrderByDistance(AirDefense start, List <AirDefense> remainingAirDefenses)
        {
            var current   = start;
            var remaining = remainingAirDefenses.ToList();

            var path = new List <AirDefense> {
                start
            };

            while (remaining.Count != 0)
            {
                var next = Closest(current, remaining);
                path.Add(next);
                remaining.Remove(next);
                current = next;
            }
            return(path);
        }
        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;
            }
        }
Esempio n. 3
0
        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);
        }
Esempio n. 4
0
        internal static void AddArt(object Item, string SType)
        {
            using (var db = new DatabaseContext())
            {
                switch (SType)
                {
                case "AirDefense":
                    AirDefense tempAD = new AirDefense();
                    foreach (PropertyInfo x in Item.GetType().GetProperties())
                    {
                        x.SetValue(tempAD, x.GetValue(Item));
                    }
                    db.Artillery.Add(tempAD);
                    break;

                case "Howitzers":
                    Howitzers tempH = new Howitzers();
                    foreach (PropertyInfo x in Item.GetType().GetProperties())
                    {
                        x.SetValue(tempH, x.GetValue(Item));
                    }
                    db.Artillery.Add(tempH);
                    break;

                case "Mortars":
                    Mortars tempM = new Mortars();
                    foreach (PropertyInfo x in Item.GetType().GetProperties())
                    {
                        x.SetValue(tempM, x.GetValue(Item));
                    }
                    db.Artillery.Add(tempM);
                    break;
                }
                db.SaveChanges();
            }
        }
        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);
        }
Esempio n. 6
0
        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);
            }
        }
Esempio n. 7
0
        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);
        }
Esempio n. 8
0
        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);
            }
        }