public static IEnumerable <int> DeployLava() { if (lava?.Count >= 2) { var count = lava.Count / 2; foreach (var t in Deploy.AtPoints(lava, new PointFT[] { AllInOnePushDeploy.FirstFunnellingPoint, AllInOnePushDeploy.SecondFunnellingPoint }, count, 0, 200, 5)) { yield return(t); } if (lava?.Count > 0) { foreach (var t in Deploy.AtPoint(lava, AllInOnePushDeploy.SecondFunnellingPoint, lava.Count)) { yield return(t); } } if (clanCastle?.Count > 0 && AllInOnePushDeploy.ClanCastleSettings > 0) { foreach (var t in Deploy.AtPoint(clanCastle, AllInOnePushDeploy.FirstFunnellingPoint)) { yield return(t); } } } else if (lava?.Count == 1) { if (clanCastle?.Count > 0 && AllInOnePushDeploy.ClanCastleSettings > 0) { foreach (var t in Deploy.AtPoint(clanCastle, AllInOnePushDeploy.FirstFunnellingPoint)) { yield return(t); } foreach (var t in Deploy.AtPoint(lava, AllInOnePushDeploy.SecondFunnellingPoint)) { yield return(t); } } else { foreach (var t in Deploy.AtPoint(lava, AllInOnePushDeploy.Origin)) { yield return(t); } } } }
public override IEnumerable <int> AttackRoutine() { Log.Info("[Deploy] Deploy start"); // Get all the units available Log.Debug("Scanning troops"); var deployElements = Deploy.GetTroops(); var spells = deployElements.Extract(DeployElementType.Spell); var tankUnits = deployElements.Extract(AttackType.Tank).ToArray(); var attackUnits = deployElements.Extract(AttackType.Damage).ToArray(); var healUnits = deployElements.Extract(AttackType.Heal).ToArray(); var waveCounter = 1; // Get starting resources LootResources preLoot = Opponent.GetAvailableLoot(); if (preLoot == null) { Log.Error("[Deploy] Milking deploy could not read available starting loot"); Attack.Surrender(); yield break; } Log.Debug($"[Deploy] Pre-attack resources - G: {preLoot.Gold}, E: {preLoot.Elixir}, DE: {preLoot.DarkElixir}"); // Make sure we wait at least 15 seconds in this attack, in case we snipe TH // Loop until surrender conditions are met while (true) { // Get deploy points for each mine that is on the outside of the base //resourcesFull = GetResourcesState(); GenerateDeployPointsFromMinesToMilk(CacheBehavior.CheckForDestroyed); if (deployPoints == null || deployPoints.Length < 1) { Log.Debug("Surrendering because deployPoints = " + deployPoints?.Length); break; } if (tankUnits.Any()) { foreach (var t in Deploy.AtPoints(tankUnits, deployPoints)) { yield return(t); } yield return(1000); } if (attackUnits.Any()) { foreach (var t in Deploy.AtPoints(attackUnits, deployPoints, 6)) { yield return(t); } yield return(1000); } if (healUnits.Any()) { foreach (var t in Deploy.AtPoints(healUnits, deployPoints)) { yield return(t); } yield return(1000); } // Wait for the wave to finish Log.Info($"[Deploy] Wave {waveCounter} Deployed. Waiting to finish..."); foreach (var t in Attack.WaitForNoResourceChange()) { yield return(t); } // Get starting resources, cache needs to be false to force a new check LootResources postLoot = Opponent.GetAvailableLoot(false); if (postLoot == null) { Log.Warning("[Deploy] Milking Deploy could not read available loot this wave"); } else { Log.Debug($"[Deploy] Wave {waveCounter} resources - G: {postLoot.Gold}, E: {postLoot.Elixir}, DE: {postLoot.DarkElixir}"); int newGold = preLoot.Gold - postLoot.Gold; int newElixir = preLoot.Elixir - postLoot.Elixir; int newDark = preLoot.DarkElixir - postLoot.DarkElixir; Log.Debug($"[Deploy] Wave {waveCounter} resource diff - G: {newGold}, E: {newElixir}, DE: {newDark}, points: {deployPoints.Length}"); if (newGold + newElixir < 5000 * deployPoints.Length) { Log.Debug("Surrendering because gained resources isn't enough"); break; } preLoot = postLoot; } waveCounter++; } // Wait for the wave to finish Log.Info("[Deploy] Deploy done. Waiting to finish..."); foreach (var t in Attack.WaitForNoResourceChange(10)) { yield return(t); } if (spells.Any(u => u.Id == DeployId.Lightning) && DarkElixirDrill.Find(CacheBehavior.ForceScan, 4).Length > 0) { Log.Debug("Level 4 or greater drills found, waiting for attack to finish."); foreach (var t in Attack.WaitForNoResourceChange(5)) { yield return(t); } foreach (var t in ZapDarkElixirDrills()) { yield return(t); } } Attack.Surrender(); }
public override IEnumerable <int> AttackRoutine() { #if DEBUG debugMode = true; #endif var visuals = new List <VisualObject>(); // 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.Info("[BabyLoon] 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 special units into their own list under respect of usersettings var heroes = deployElements .Extract(u => (UserSettings.UseKing && u.ElementType == DeployElementType.HeroKing) || (UserSettings.UseQueen && u.ElementType == DeployElementType.HeroQueen) || (UserSettings.UseWarden && u.ElementType == DeployElementType.HeroWarden)) .ToList(); var clanCastle = deployElements.ExtractOne(u => u.ElementType == DeployElementType.ClanTroops && UserSettings.UseClanTroops); // remove left special units so we won't use them accidentially deployElements. RemoveAll(u => u.ElementType == DeployElementType.ClanTroops || u.ElementType == DeployElementType.HeroKing || u.ElementType == DeployElementType.HeroQueen || u.ElementType == DeployElementType.HeroWarden); // extract units into their own lists in order to use them for more specific behaviours var lightningSpell = deployElements.ExtractOne(x => x.Id == DeployId.Lightning); var earthQuakeSpell = deployElements.ExtractOne(x => x.Id == DeployId.Earthquake); var rageSpell = deployElements.ExtractOne(x => x.Id == DeployId.Rage); var freezeSpels = deployElements.ExtractOne(x => x.Id == DeployId.Freeze); var tanks = deployElements.Extract(AttackType.Tank).ToArray(); var balloon = deployElements.ExtractOne(x => x.Id == DeployId.Balloon); var babyDragon = deployElements.ExtractOne(x => x.Id == DeployId.BabyDragon); var wallBreakers = deployElements.ExtractOne(x => x.Id == DeployId.WallBreaker); var damageDealers = deployElements.Extract(AttackType.Damage).OrderByDescending(x => x.UnitData.HP).ToArray(); // 1. Find the redpoint which is the closest to the TownHall var townHall = TownHall.Find(); var townHallPosition = townHall == null ? new PointFT(0, 0) : townHall.Location.GetCenter(); var closestThRedPoint = GameGrid.RedPoints.OrderBy(x => x.DistanceSq(townHallPosition)).First(); visuals.Add(new PointsObject("Closest TH Point", Color.FromArgb(200, Color.Cyan), new PointFT[] { closestThRedPoint })); // 2. Prepare DeployPoint Calculation var babyDragonSpread = babyDragon?.Count * 0.031 ?? 0; var spreadFactor = 0.7 + babyDragonSpread; double minAngle = closestThRedPoint.Angle - spreadFactor; double maxAngle = closestThRedPoint.Angle + spreadFactor; double twoPi = 2 * Math.PI; double circleStart = -Math.PI; double circleEnd = Math.PI; if (minAngle < -Math.PI) { circleEnd = minAngle + twoPi; minAngle = -Math.PI; } else if (maxAngle > Math.PI) { circleStart = maxAngle - twoPi; maxAngle = Math.PI; } // 1. Condition: All Points between minAngle and MaxAngle // OR // 2. Condition: All Points between 0 and circleStart // OR // 3. Condition: All Points between circleEnd and twoPi var potentialDeployPoints = GameGrid.RedPoints.Where(x => x.Angle >= minAngle && x.Angle <= maxAngle || x.Angle >= -Math.PI && x.Angle <circleStart || x.Angle> circleEnd && x.Angle <= Math.PI).ToArray(); potentialDeployPoints = OrderDeployPoints(potentialDeployPoints); visuals.Add(new PointsObject("Potential DeployPoints", Color.FromArgb(100, Color.Red), potentialDeployPoints, 1)); // 3. If possible - zapquake 2nd closest AirDefense if (earthQuakeSpell?.Count >= 1 && lightningSpell?.Count >= 2) { var target = FindZapQuakeTarget(closestThRedPoint, visuals); if (target != null) { Log.Info("[BabyLoon] Going to ZapQuake second closest AirDefense from deployside"); foreach (var t in ZapQuakeTarget(target.Location.GetCenter(), earthQuakeSpell, lightningSpell)) { yield return(t); } } else { Log.Info("[BabyLoon] Couldn't find more than 1 AirDefense - hence skipping ZapQuake"); } } else { Log.Debug("[BabyLoon] Not enough spells for ZapQuake available"); } // 4. Calculate all deployPoints var babyDragonDeployPoints = CalculateBabyDragonDeployPoints(babyDragon, potentialDeployPoints, visuals); var balloonDeployPoints = CalculateBalloonDeployPoints(balloon, potentialDeployPoints, visuals); var rageDeployPoints = CalculateRageDeployPoints(rageSpell, potentialDeployPoints, visuals); var tankDeployPoints = CalculateTankDeployPoints(tanks, potentialDeployPoints, visuals); // Deploy Clancastle 2 tiles away from the closestThRedPoint var clanCastleDeployPoint = closestThRedPoint.TransformPositionAlongAxis(2).Constrain(); // 5. Deploy Tanks and ClanCastle in the center of the DeploySide if (tanks.Length > 0) { foreach (var tank in tanks) { Log.Info($"[BabyLoon] Deploying {tank.PrettyName} x{tank.Count}"); } // If there is just one tank use closestThRedPoint as deploypoint if (tanks.Sum(x => x.Count) == 1) { while (tanks.Sum(x => x.Count) > 0) { var initialTroopCount = tanks.Sum(x => x.Count); foreach (var t in Deploy.AtPoint(tanks, closestThRedPoint)) { yield return(t); } // Prevent an infinite loop if deploy point is inside of the redzone if (tanks.Sum(x => x.Count) != initialTroopCount) { continue; } foreach (var tank in tanks) { Log.Warning($"[BabyLoon] Couldn't deploy x{tank.Count} {tank.PrettyName}"); } break; } } // If there is more than one tank to deploy, deploy them along the attacking side while (tanks.Sum(x => x.Count) > 0) { var initialTroopCount = tanks.Sum(x => x.Count); foreach (var t in Deploy.AtPoints(tanks, tankDeployPoints)) { yield return(t); } // Prevent an infinite loop if deploy point is inside of the redzone if (tanks.Sum(x => x.Count) != initialTroopCount) { continue; } foreach (var tank in tanks) { Log.Warning($"[BabyLoon] Couldn't deploy x{tank.Count} {tank.PrettyName}"); } break; } } // 6. Deploy BabyDragons if (babyDragon?.Count > 0) { Log.Info($"[BabyLoon] Deploying {babyDragon.PrettyName} x{babyDragon.Count}"); while (babyDragon.Count > 0) { var initialBabyDragonCount = babyDragon.Count; foreach (var t in Deploy.AtPoints(babyDragon, babyDragonDeployPoints, 1, 50)) { yield return(t); } // Prevent an infinite loop if deploy points are inside of the redzone if (babyDragon.Count != initialBabyDragonCount) { continue; } Log.Warning($"[BabyLoon] Couldn't deploy {babyDragon.PrettyName}"); break; } // Humanizing pause yield return(500); } // 7. Deploy balloons if (balloon?.Count > 0) { Log.Debug($"[BabyLoon] Wait 2 seconds before deploying {balloon.PrettyName} x{balloon.Count}!"); yield return(2000); Log.Info($"[BabyLoon] Deploying {balloon.PrettyName} x{balloon.Count}"); while (balloon.Count > 0) { var initialBalloonCount = balloon.Count; foreach (var t in Deploy.AtPoints(balloon, balloonDeployPoints, 1, 50)) { yield return(t); } // Prevent an infinite loop if deploy points are inside of the redzone if (balloon.Count != initialBalloonCount) { continue; } Log.Warning($"[BabyLoon] Couldn't deploy {balloon.PrettyName}"); break; } } // 8. Deploy Ragespells if (rageSpell?.Count > 0) { Log.Info($"[BabyLoon] Deploying {rageSpell.PrettyName} x{rageSpell.Count}"); while (rageSpell.Count > 0) { var initialRageSpellCount = rageSpell.Count; foreach (var t in Deploy.AtPoints(rageSpell, rageDeployPoints, 1, 50)) { yield return(t); } // Prevent an infinite loop if deploy points are inside of the redzone if (balloon.Count != initialRageSpellCount) { continue; } Log.Warning($"[BabyLoon] Couldn't deploy {rageSpell.PrettyName}"); break; } } // 9. Deploy all other troops if (damageDealers.Length > 0) { foreach (var damagedealer in damageDealers) { Log.Info($"[BabyLoon] Deploying {damagedealer.PrettyName} x{damagedealer.Count}"); } while (damageDealers.Sum(x => x.Count) > 0) { var initialTroopCount = damageDealers.Sum(x => x.Count); foreach (var t in Deploy.AtPoint(damageDealers, closestThRedPoint)) { yield return(t); } // Prevent an infinite loop if deploy point is inside of the redzone if (tanks.Sum(x => x.Count) != initialTroopCount) { continue; } foreach (var damagedealer in damageDealers) { Log.Warning($"[BabyLoon] Couldn't deploy x{damagedealer.Count} {damagedealer.PrettyName}"); } break; } } // 10. Deploy Heroes and watch them to activate abilities if (heroes.Any()) { var heroDeployPoints = GameGrid.RedPoints.OrderBy(x => x.DistanceSq(closestThRedPoint)).Take(10).ToArray(); heroDeployPoints = heroDeployPoints.Select(x => x.TransformPositionAlongAxis(4).Constrain()).ToArray(); foreach (var hero in heroes.Where(u => u.Count > 0)) { Log.Info($"[BabyLoon] Deploying {hero.PrettyName}"); foreach (var t in Deploy.AtPoints(hero, heroDeployPoints)) { yield return(t); } } Deploy.WatchHeroes(heroes, 7000); } // 11. Deploy Wallbreakers in 3 unit stacks near heroes while (wallBreakers?.Count > 0) { // Shift Heroes' deploypoint by 1 tile in both directions to prevent deployment on the same spot (wallbreakers may die due to splashdamage against heroes) var wallbreakerDeployPoints = GameGrid.RedPoints.OrderBy(x => x.DistanceSq(closestThRedPoint)).Take(15).ToArray(); var count = wallBreakers.Count; Log.Info($"[BabyLoon] Deploying {wallBreakers.PrettyName} x3"); foreach (var t in Deploy.AtPoints(wallBreakers, wallbreakerDeployPoints, 3)) { yield return(t); } // prevent infinite loop if deploy point is on red if (wallBreakers.Count != count) { continue; } Log.Warning($"[BabyLoon] Couldn't deploy {wallBreakers.PrettyName}"); break; } if (clanCastle?.Count > 0) { Log.Info($"[BabyLoon] Deploying {clanCastle.PrettyName}"); foreach (var t in Deploy.AtPoint(clanCastle, clanCastleDeployPoint)) { yield return(t); } } if (debugMode) { VisualizeDeployment(visuals); } }
public override IEnumerable <int> AttackRoutine() { Log.Info($"{Tag}Deploy start - V.{Assembly.GetExecutingAssembly().GetName().Version.ToString()}"); Log.Debug($"{Tag}Attack Identifier: [{AttackId}] - (Links Debug Images to Log File)"); var attackStartTime = DateTime.UtcNow; //Write out all the Current Algorithm Settings... var settings = $"{Tag}Current Custom Settings Values:{Environment.NewLine}"; foreach (var setting in AllCurrentSettings) { settings += $"{setting.InstanceType} Setting - '{setting.Name}' Value: {setting.Value}{Environment.NewLine}"; } Log.Debug(settings); var waveCounter = 1; //Check if we can snipe the town hall, and if so, what are the Deployment points for Gruns/Ranged. TownHall townHall = TownHall.Find(CacheBehavior.Default); Target townHallTarget = townHall.GetSnipeDeployPoints(); // Get starting resources LootResources preLoot = Opponent.GetAvailableLoot(); if (preLoot == null) { Log.Error($"{Tag}Could not read available starting loot"); Attack.Surrender(); yield break; } Log.Info($"{Tag}Pre-attack resources - G: {preLoot.Gold}, E: {preLoot.Elixir}, DE: {preLoot.DarkElixir}"); var collectorCacheBehavior = CacheBehavior.ForceScan; var collectorCount = 0; var acceptableTargetRange = CurrentSetting("Acceptable Target Range"); acceptableTargetRange = acceptableTargetRange * acceptableTargetRange; var activeBase = !Opponent.IsDead(); var clanCastleDeployed = false; bool watchHeroes = false; bool kingDeployed = false; bool queenDeployed = false; bool wardenDeployed = false; // Loop until surrender conditions are met while (true) { // Get all the units available Log.Info($"{Tag}Scanning troops for wave {waveCounter}"); var allElements = Attack.GetAvailableDeployElements(); var deployElements = allElements.Where(x => x.UnitData != null).ToArray(); var rangedUnits = deployElements.Where(x => x.IsRanged == true && x.ElementType == DeployElementType.NormalUnit && x.UnitData.AttackType == AttackType.Damage); IEnumerable <DeployElement> gruntUnits = null; IEnumerable <DeployElement> tankUnits = null; if (CurrentSetting("Use Valkyries as Tanks") == 0) { gruntUnits = deployElements.Where(x => x.IsRanged == false && x.ElementType == DeployElementType.NormalUnit && x.UnitData.AttackType == AttackType.Damage); tankUnits = deployElements.Where(x => x.IsRanged == false && x.ElementType == DeployElementType.NormalUnit && x.UnitData.AttackType == AttackType.Tank); } else { //Reclassify Valks as Tanks! gruntUnits = deployElements.Where(x => x.IsRanged == false && x.ElementType == DeployElementType.NormalUnit && x.UnitData.AttackType == AttackType.Damage && x.Id != DeployId.Valkyrie); tankUnits = deployElements.Where(x => (x.IsRanged == false && x.ElementType == DeployElementType.NormalUnit && x.UnitData.AttackType == AttackType.Tank) || x.Id == DeployId.Valkyrie); } List <DeployElement> king = allElements.Where(x => x.IsHero && x.Name.ToLower().Contains("king")).ToList(); List <DeployElement> queen = allElements.Where(x => x.IsHero && x.Name.ToLower().Contains("queen")).ToList(); List <DeployElement> warden = allElements.Where(x => x.IsHero && x.Name.ToLower().Contains("warden")).ToList(); List <DeployElement> allHeroes = new List <DeployElement>(); allHeroes.AddRange(king); allHeroes.AddRange(queen); allHeroes.AddRange(warden); //Write out all the unit pretty names we found... Log.Debug($"{Tag}Deployable Troops (wave {waveCounter}): {ToUnitString(allElements)}"); var outputDebugImage = (CurrentSetting("Debug Mode") == 1); double avgFillState = 0; double avgCollectorLvl = 0; //First time through force a Scan... after the first wave always recheck for Destroyed ones... Target[] targets = HumanLikeAlgorithms.GenerateTargets(algorithmName, acceptableTargetRange, IgnoreGold, IgnoreElixir, AttackId, out avgFillState, out avgCollectorLvl, collectorCacheBehavior, outputDebugImage, activeBase); collectorCount = targets.Length; Target reminderTarget = null; if (collectorCount > 0) { reminderTarget = targets[0]; } //Reorder the Deploy points so they look more human like when attacking. var groupedTargets = targets.ReorderToClosestNeighbor().GroupCloseTargets(); collectorCacheBehavior = CacheBehavior.CheckForDestroyed; if (collectorCount < 1) { Log.Info($"{Tag}Collectors Remaining = {collectorCount} - Deployment Done. Exiting Attack Loop."); break; } int meleCount = 0; int rangedCount = 0; int tankCount = 0; if (CurrentSetting("Deploy All Troops Mode") == 0) { //Determine Counts of each type of unit to use... meleCount = CurrentSetting("Ground Units Per Target"); rangedCount = CurrentSetting("Ranged Units Per Target"); tankCount = CurrentSetting("Tank Units Per Target"); } else { //Get the total count of Valid Targets. (Including Town Hall if there is one.) int totalTargetCount = targets.Length; if (townHallTarget.ValidTarget) { totalTargetCount++; } //Will be the largest int without remainder. meleCount = gruntUnits.TotalUnitCount() / totalTargetCount; rangedCount = rangedUnits.TotalUnitCount() / totalTargetCount; tankCount = tankUnits.TotalUnitCount() / totalTargetCount; //Make sure if there are less than 1 per target, but still more than zero. set to 1. if (tankCount <= 0 && tankUnits.TotalUnitCount() > 0) { tankCount = 1; } if (rangedCount <= 0 && rangedUnits.TotalUnitCount() > 0) { rangedCount = 1; } if (meleCount <= 0 && gruntUnits.TotalUnitCount() > 0) { meleCount = 1; } } if (townHallTarget.ValidTarget) { //Drop some Grunt and Ranged troups on the TH as well as collectors. //If there are Teslas around it, oh well. we only spent 9-12 units of each type trying. if (gruntUnits.Any()) { Log.Info($"{Tag}TH Snipe Dead {meleCount} Grunts Near: X:{townHallTarget.DeployGrunts.X} Y:{townHallTarget.DeployGrunts.Y}"); foreach (var t in Deploy.AtPoints(gruntUnits.FilterTypesByCount(), townHallTarget.DeployGrunts.RandomPointsInArea(_thDeployRadius, meleCount), 1)) { yield return(t); } yield return(Rand.Int(300, 500)); //Wait } if (rangedUnits.Any()) { Log.Info($"{Tag}TH Snipe Dead {rangedCount} Ranged Near: X:{townHallTarget.DeployRanged.X} Y:{townHallTarget.DeployRanged.Y}"); foreach (var t in Deploy.AtPoints(rangedUnits.FilterTypesByCount(), townHallTarget.DeployRanged.RandomPointsInArea(_thDeployRadius, rangedCount), 1)) { yield return(t); } yield return(Rand.Int(300, 500)); //Wait } if (UserSettings.UseClanTroops) { var clanCastle = allElements.FirstOrDefault(u => u.ElementType == DeployElementType.ClanTroops); clanCastleDeployed = true; if (clanCastle?.Count > 0) { Log.Info($"{Tag}Deploying Clan Castle Near Town Hall"); foreach (var t in Deploy.AtPoint(clanCastle, townHallTarget.DeployRanged, clanCastle.Count)) { yield return(t); } } else { Log.Info($"{Tag}No Clan Castle Troops found to Deploy on Town Hall..."); } } //Only do this once. townHallTarget.ValidTarget = false; } //Determine the index of the 1st and 2nd largest set of targets all in a row. var largestSetIndex = -1; int largestSetCount = 0; var secondLargestSetIndex = -1; int secondLargestSetCount = 0; for (int i = 0; i < groupedTargets.Count; i++) { if (groupedTargets[i].Length > largestSetCount) { secondLargestSetCount = largestSetCount; secondLargestSetIndex = largestSetIndex; largestSetCount = groupedTargets[i].Length; largestSetIndex = i; } else if (groupedTargets[i].Length > secondLargestSetCount) { secondLargestSetCount = groupedTargets[i].Length; secondLargestSetIndex = i; } } Log.Info($"{Tag}{groupedTargets.Count} Target Groups, Largest has {largestSetCount} targets, Second Largest {secondLargestSetCount} targets."); if (largestSetCount <= 1) { Log.Info($"{Tag}No group of two or more targets found - Skipping deploy of Heros & Clan Castle."); } //Deploy Barch Units - In Groups on Sets of collectors that are close together. for (int p = 0; p < groupedTargets.Count; p++) { //Deploy Tanks on the Set of Targets. (If any exist) for (int i = 0; i < groupedTargets[p].Length; i++) { var gruntDeployPoint = groupedTargets[p][i].DeployGrunts; //First Deploy tanks if (tankUnits.Any()) { Log.Debug($"{Tag}Deploying {tankCount} Tank Units on {groupedTargets[p][i].Name} {p + 1}-{i}"); foreach (var t in Deploy.AtPoints(tankUnits.FilterTypesByCount(), gruntDeployPoint.RandomPointsInArea(_collectorDeployRadius, tankCount), 1)) { yield return(t); } yield return(Rand.Int(10, 40)); //Wait } } if (gruntUnits.Any()) { //Pause inbetween switching units. yield return(Rand.Int(90, 100)); //Wait } //Deploy Grunts on the Set of Targets. for (int i = 0; i < groupedTargets[p].Length; i++) { var gruntDeployPoint = groupedTargets[p][i].DeployGrunts; //Next Deploy Ground troops if (gruntUnits.Any()) { Log.Debug($"{Tag}Deploying {meleCount} Ground Units on {groupedTargets[p][i].Name} {p + 1}-{i}"); foreach (var t in Deploy.AtPoints(gruntUnits.FilterTypesByCount(), gruntDeployPoint.RandomPointsInArea(_collectorDeployRadius, meleCount), 1)) { yield return(t); } yield return(Rand.Int(10, 40)); //Wait } } if (rangedUnits.Any()) { //Pause inbetween switching units. yield return(Rand.Int(90, 100)); //Wait } //Deploy Ranged units on same set of Targets. for (int i = 0; i < groupedTargets[p].Length; i++) { var rangedDeployPoint = groupedTargets[p][i].DeployRanged; if (rangedUnits.Any()) { Log.Debug($"{Tag}Deploying {rangedCount} Ranged Units on {groupedTargets[p][i].Name} {p + 1}-{i}"); foreach (var t in Deploy.AtPoints(rangedUnits.FilterTypesByCount(), rangedDeployPoint.RandomPointsInArea(_collectorDeployRadius, rangedCount), 1)) { yield return(t); } yield return(Rand.Int(40, 50)); //Wait } } if (largestSetIndex == p && largestSetCount >= 2 && waveCounter == 1) { //We are currently deploying to the largest set of Targets - AND its a set of 2 or more. //Preferrably Drop All Heros on this set (2nd Target in the set.) reminderTarget = groupedTargets[p][1]; if (!clanCastleDeployed && UserSettings.UseClanTroops) { var clanCastle = allElements.FirstOrDefault(u => u.ElementType == DeployElementType.ClanTroops); if (clanCastle?.Count > 0) { Log.Info($"{Tag}Deploying Clan Castle on Largest set of Targets: {largestSetCount} targets."); foreach (var t in Deploy.AtPoint(clanCastle, groupedTargets[p][1].DeployRanged, clanCastle.Count)) { yield return(t); } } else { Log.Info($"{Tag}No Clan Castle Troops found to Deploy..."); } clanCastleDeployed = true; } if (UserSettings.UseKing && king.Any() && !kingDeployed) { yield return(Rand.Int(90, 100)); //Wait before dropping King Log.Info($"{Tag}Deploying King on largest set of targets: {largestSetCount} targets."); foreach (var t in Deploy.AtPoint(king[0], groupedTargets[p][1].DeployGrunts)) { yield return(t); } yield return(Rand.Int(200, 500)); //Wait kingDeployed = true; watchHeroes = true; } if (UserSettings.UseQueen && queen.Any() && !queenDeployed) { yield return(Rand.Int(90, 100)); //Wait before dropping Queen Log.Info($"{Tag}Deploying Queen on largest set of targets: {largestSetCount} targets."); foreach (var t in Deploy.AtPoint(queen[0], groupedTargets[p][1].DeployRanged)) { yield return(t); } yield return(Rand.Int(200, 500)); //Wait queenDeployed = true; watchHeroes = true; } if (UserSettings.UseWarden && warden.Any() && !wardenDeployed) { Log.Info($"{Tag}Deploying Warden on largest set of targets: {largestSetCount} targets."); foreach (var t in Deploy.AtPoint(warden[0], groupedTargets[p][1].DeployRanged)) { yield return(t); } yield return(Rand.Int(200, 500)); //Wait wardenDeployed = true; watchHeroes = true; } //Now that the first round of deploying is done, watch any heros if necessary. if (watchHeroes) { //Watch Heros and Hit ability when they get low. Log.Info($"{Tag}Watching heros to activate abilities when health gets Low."); Deploy.WatchHeroes(allHeroes); watchHeroes = false; //Only do this once through the loop. } } yield return(Rand.Int(90, 100)); //Wait before switching units back to Grutns and deploying on next set of targets. } //If Deploy ALL Troops is turned on, if (CurrentSetting("Deploy All Troops Mode") == 1 && reminderTarget != null) { //Deploy the Reminder of troops on the LARGEST Group of targets. //First Deploy tanks foreach (var units in tankUnits) { if (units?.Count > 0) { Log.Debug($"{Tag}Deploying Reminder of {units.PrettyName} Tank Units ({units.Count}) on {reminderTarget.Name}"); foreach (var t in Deploy.AtPoint(units, reminderTarget.DeployGrunts, units.Count)) { yield return(t); } yield return(Rand.Int(2000, 3000)); //Wait } } //Next Deploy Grunts foreach (var units in gruntUnits) { if (units?.Count > 0) { Log.Debug($"{Tag}Deploying Reminder of {units.PrettyName} Mele Units ({units.Count}) on {reminderTarget.Name}"); foreach (var t in Deploy.AtPoint(units, reminderTarget.DeployGrunts, units.Count)) { yield return(t); } yield return(Rand.Int(100, 200)); //Wait } } //Next Deploy Ranged foreach (var units in rangedUnits) { if (units?.Count > 0) { Log.Debug($"{Tag}Deploying Reminder of {units.PrettyName} Ranged Units ({units.Count}) on {reminderTarget.Name}"); foreach (var t in Deploy.AtPoint(units, reminderTarget.DeployRanged, units.Count)) { yield return(t); } yield return(Rand.Int(100, 200)); //Wait } } //There is only ONE wave in Deploy all troops mode... break; } //wait a random number of seconds before the next round on all Targets... yield return(Rand.Int(5000, 7000)); // Get starting resources, cache needs to be false to force a new check LootResources postLoot = Opponent.GetAvailableLoot(false); if (postLoot == null) { Log.Warning($"{Tag}could not read available loot this wave"); postLoot = new LootResources() { Gold = -1, Elixir = -1, DarkElixir = -1 }; } Log.Info($"{Tag}Wave {waveCounter} resources - G: {postLoot.Gold}, E: {postLoot.Elixir}, DE: {postLoot.DarkElixir}"); int newGold = preLoot.Gold - postLoot.Gold; int newElixir = preLoot.Elixir - postLoot.Elixir; int newDark = preLoot.DarkElixir - postLoot.DarkElixir; Log.Info($"{Tag}Wave {waveCounter} resource diff - G: {newGold}, E: {newElixir}, DE: {newDark}, Collectors: {collectorCount}"); //Check to see if we are getting enough Resources... if (postLoot.Gold + postLoot.Elixir + postLoot.DarkElixir >= 0) { if (newGold + newElixir < 3000 * collectorCount) { Log.Info($"{Tag}Stopping Troop Deployment because gained resources isn't enough"); break; } preLoot = postLoot; } waveCounter++; } if (CurrentSetting("Debug Mode") == 1) { Log.Debug($"{Tag}Deployment End. Taking last debug Screenshot."); HumanLikeAlgorithms.SaveBasicDebugScreenShot(algorithmName, AttackId, "Battle End"); } //We broke out of the attack loop - allow attack to end how specified in the General Bot Settings... Log.Info($"{Tag} <<<<<< End of Human Barch Algorithm >>>>>>"); }
public override IEnumerable <int> AttackRoutine() { Log.Info($"[Human Barch] Deploy start - V.{Assembly.GetExecutingAssembly().GetName().Version.ToString()}"); var waveCounter = 1; //Check if we can snipe the town hall, and if so, what are the Deployment points for Gruns/Ranged. TownHall townHall = TownHall.Find(CacheBehavior.Default); Target townHallTarget = townHall.GetSnipeDeployPoints(); // Get starting resources LootResources preLoot = Opponent.GetAvailableLoot(); if (preLoot == null) { Log.Error("[Human Barch] Could not read available starting loot"); Attack.Surrender(); yield break; } Log.Info($"[Human Barch] Pre-attack resources - G: {preLoot.Gold}, E: {preLoot.Elixir}, DE: {preLoot.DarkElixir}"); var collectorCacheBehavior = CacheBehavior.Default; var collectorCount = 0; var isDead = Opponent.IsDead(true); // Loop until surrender conditions are met while (true) { // Get all the units available Log.Info($"[Human Barch] Scanning troops for wave {waveCounter}"); var allElements = Attack.GetAvailableDeployElements(); var deployElements = allElements.Where(x => x.UnitData != null).ToArray(); var rangedUnits = deployElements.Where(x => x.IsRanged == true && x.ElementType == DeployElementType.NormalUnit && x.UnitData.AttackType == AttackType.Damage); var gruntUnits = deployElements.Where(x => x.IsRanged == false && x.ElementType == DeployElementType.NormalUnit && x.UnitData.AttackType == AttackType.Damage); List <DeployElement> king = allElements.Where(x => x.IsHero && x.Name.ToLower().Contains("king")).ToList(); List <DeployElement> queen = allElements.Where(x => x.IsHero && x.Name.ToLower().Contains("queen")).ToList(); List <DeployElement> allHeroes = new List <DeployElement>(); allHeroes.AddRange(king); allHeroes.AddRange(queen); bool watchHeroes = false; //Dont Deploy any Tank Units... even if we have them. if (!isDead) { if (townHallTarget.ValidTarget) { //Before we enter the main attack routine... If there is an exposed TH, Snipe it. //If there are Teslas around it, oh well. we only spent 9-12 units of each type trying. if (gruntUnits.Any()) { var gruntsToDeploy = Rand.Int(5, 15); Log.Info($"[Human Barch] Sniping Town Hall {gruntsToDeploy} Grunts Near: X:{townHallTarget.DeployGrunts.X} Y:{townHallTarget.DeployGrunts.Y}"); foreach (var t in Deploy.AtPoints(gruntUnits.FilterTypesByCount(), townHallTarget.DeployGrunts.RandomPointsInArea(_thDeployRadius, gruntsToDeploy), 1, Rand.Int(10, 40), Rand.Int(10, 40))) { yield return(t); } //Wait almost a second yield return(Rand.Int(300, 500)); //Wait } if (rangedUnits.Any()) { var rangedToDeploy = Rand.Int(5, 15); Log.Info($"[Human Barch] Sniping Town Hall {rangedToDeploy} Ranged Near: X:{townHallTarget.DeployRanged.X} Y:{townHallTarget.DeployRanged.Y}"); foreach (var t in Deploy.AtPoints(rangedUnits.FilterTypesByCount(), townHallTarget.DeployRanged.RandomPointsInArea(_thDeployRadius, rangedToDeploy), 1, Rand.Int(10, 40), Rand.Int(10, 40))) { yield return(t); } //Wait almost a second yield return(Rand.Int(300, 500)); //Wait } //If we dont have a star yet, Drop the King... if (!Attack.HaveAStar()) { if (UserSettings.UseKing && king.Any()) { Log.Info($"[Human Barch] Deploying King at: X:{townHallTarget.DeployGrunts.X} Y:{townHallTarget.DeployGrunts.Y}"); foreach (var t in Deploy.AtPoint(king[0], townHallTarget.DeployGrunts)) { yield return(t); } yield return(Rand.Int(900, 1000)); //Wait watchHeroes = true; } //Deploy the Queen if (UserSettings.UseQueen && queen.Any()) { Log.Info($"[Human Barch] Deploying Queen at: X:{townHallTarget.DeployRanged.X} Y:{townHallTarget.DeployRanged.Y}"); foreach (var t in Deploy.AtPoint(queen[0], townHallTarget.DeployRanged)) { yield return(t); } yield return(Rand.Int(900, 1000)); //Wait watchHeroes = true; } if (watchHeroes) { //Watch Heros and Hit ability when they get low. Deploy.WatchHeroes(allHeroes); watchHeroes = false; //Only do this once through the loop. } } //Only try once to snipe the town hall when deploying waves. townHallTarget.ValidTarget = false; } } else { //First time through use cached... after the first wave always recheck for Destroyed ones... Target[] targets = HumanLikeAlgorithms.GenerateTargets(_minimumAttackDistanceToCollectors, IgnoreGold, IgnoreElixir, collectorCacheBehavior); collectorCount = targets.Length; //Reorder the Deploy points so they look more human like when attacking. var groupedTargets = targets.ReorderToClosestNeighbor().GroupCloseTargets(); collectorCacheBehavior = CacheBehavior.CheckForDestroyed; if (collectorCount < 1) { Log.Info($"[Human Barch] Surrendering - Collectors Remaining = {collectorCount}"); // Wait for the wave to finish Log.Info("[Human Barch] Deploy done. Waiting to finish..."); var x = Attack.WatchResources(10d).Result; break; } if (townHallTarget.ValidTarget) { //Drop some Grunt and Ranged troups on the TH as well as collectors. //If there are Teslas around it, oh well. we only spent 9-12 units of each type trying. if (gruntUnits.Any()) { var gruntsToDeploy = Rand.Int(4, 6); Log.Info($"[Human Barch] + TH Snipe Dead {gruntsToDeploy} Grunts Near: X:{townHallTarget.DeployGrunts.X} Y:{townHallTarget.DeployGrunts.Y}"); foreach (var t in Deploy.AtPoints(gruntUnits.FilterTypesByCount(), townHallTarget.DeployGrunts.RandomPointsInArea(_thDeployRadius, gruntsToDeploy), 1, Rand.Int(10, 40), Rand.Int(10, 40))) { yield return(t); } yield return(Rand.Int(300, 500)); //Wait } if (rangedUnits.Any()) { var rangedToDeploy = Rand.Int(4, 6); Log.Info($"[Human Barch] + TH Snipe Dead {rangedToDeploy} Ranged Near: X:{townHallTarget.DeployRanged.X} Y:{townHallTarget.DeployRanged.Y}"); foreach (var t in Deploy.AtPoints(rangedUnits.FilterTypesByCount(), townHallTarget.DeployRanged.RandomPointsInArea(_thDeployRadius, rangedToDeploy), 1, Rand.Int(10, 40), Rand.Int(10, 40))) { yield return(t); } yield return(Rand.Int(300, 500)); //Wait } //Only do this once. townHallTarget.ValidTarget = false; } //Determine the index of the 1st and 2nd largest set of targets all in a row. var largestSetIndex = -1; int largestSetCount = 0; var secondLargestSetIndex = -1; int secondLargestSetCount = 0; for (int i = 0; i < groupedTargets.Count; i++) { if (groupedTargets[i].Length > largestSetIndex) { secondLargestSetCount = largestSetCount; secondLargestSetIndex = largestSetIndex; largestSetCount = groupedTargets[i].Length; largestSetIndex = i; } else if (groupedTargets[i].Length > secondLargestSetIndex) { secondLargestSetCount = groupedTargets[i].Length; secondLargestSetIndex = i; } } Log.Info($"[Human Barch] {groupedTargets.Count} Target Groups, Largest has {largestSetCount} targets, Second Largest {secondLargestSetCount} targets."); //Deploy Barch Units - In Groups on Sets of collectors that are close together. for (int p = 0; p < groupedTargets.Count; p++) { //Deploy Grunts on the Set of Targets. for (int i = 0; i < groupedTargets[p].Length; i++) { var gruntDeployPoint = groupedTargets[p][i].DeployGrunts; if (gruntUnits.Any()) { int decreaseFactor = 0; if (i > 0) { decreaseFactor = (int)Math.Ceiling(i / 2d); } var gruntsAtCollector = (Rand.Int(6, 8) - decreaseFactor); Log.Info($"[Human Barch] {gruntsAtCollector} Grunts Around Point: X:{gruntDeployPoint.X} Y:{gruntDeployPoint.Y}"); foreach (var t in Deploy.AtPoints(gruntUnits.FilterTypesByCount(), gruntDeployPoint.RandomPointsInArea(_collectorDeployRadius, gruntsAtCollector), 1, Rand.Int(10, 40))) { yield return(t); } yield return(Rand.Int(10, 40)); //Wait } } //Pause inbetween switching units. yield return(Rand.Int(90, 100)); //Wait if (secondLargestSetIndex == p && secondLargestSetCount >= 3) { //We are currently deploying to the 2nd largest set of Targets - AND its a set of 3 or more. //Drop the King on the 2nd Target in the set. if (UserSettings.UseKing && king.Any()) { Log.Info($"[Human Barch] Deploying King at: X:{groupedTargets[p][1].DeployGrunts.X} Y:{groupedTargets[p][1].DeployGrunts.Y}"); foreach (var t in Deploy.AtPoint(king[0], groupedTargets[p][1].DeployGrunts)) { yield return(t); } yield return(Rand.Int(900, 1000)); //Wait watchHeroes = true; } } if (largestSetIndex == p && largestSetCount >= 3) { //We are currently deploying to the largest set of Targets - AND its a set of 3 or more. //Drop the Queen on the 2nd Target in the set. if (UserSettings.UseQueen && queen.Any()) { yield return(Rand.Int(90, 100)); //Wait before dropping Queen Log.Info($"[Human Barch] Deploying Queen at: X:{groupedTargets[p][1].DeployRanged.X} Y:{groupedTargets[p][1].DeployRanged.Y}"); foreach (var t in Deploy.AtPoint(queen[0], groupedTargets[p][1].DeployRanged)) { yield return(t); } yield return(Rand.Int(900, 1000)); //Wait watchHeroes = true; } } if (watchHeroes) { //Watch Heros and Hit ability when they get low. Deploy.WatchHeroes(allHeroes); watchHeroes = false; //Only do this once through the loop. } //Deploy Ranged units on same set of Targets. for (int i = 0; i < groupedTargets[p].Length; i++) { var rangedDeployPoint = groupedTargets[p][i].DeployRanged; if (rangedUnits.Any()) { int decreaseFactor = 0; if (i > 0) { decreaseFactor = (int)Math.Ceiling(i / 2d); } var rangedAtCollector = (Rand.Int(5, 7) - decreaseFactor); Log.Info($"[Human Barch] {rangedAtCollector} Ranged Around Point: X:{rangedDeployPoint.X} Y:{rangedDeployPoint.Y}"); foreach (var t in Deploy.AtPoints(rangedUnits.FilterTypesByCount(), rangedDeployPoint.RandomPointsInArea(_collectorDeployRadius, rangedAtCollector), 1, Rand.Int(10, 40))) { yield return(t); } yield return(Rand.Int(40, 50)); //Wait } } yield return(Rand.Int(90, 100)); //Wait before switching units back to Grutns and deploying on next set of targets. } } //Never deploy any Healing type Units. //wait a random number of seconds before the next round on all Targets... yield return(Rand.Int(2000, 5000)); // Get starting resources, cache needs to be false to force a new check LootResources postLoot = Opponent.GetAvailableLoot(false); if (postLoot == null) { Log.Warning($"[Human Barch] Human Barch Deploy could not read available loot this wave"); postLoot = new LootResources() { Gold = -1, Elixir = -1, DarkElixir = -1 }; } Log.Info($"[Human Barch] Wave {waveCounter} resources - G: {postLoot.Gold}, E: {postLoot.Elixir}, DE: {postLoot.DarkElixir}"); int newGold = preLoot.Gold - postLoot.Gold; int newElixir = preLoot.Elixir - postLoot.Elixir; int newDark = preLoot.DarkElixir - postLoot.DarkElixir; Log.Info($"[Human Barch] Wave {waveCounter} resource diff - G: {newGold}, E: {newElixir}, DE: {newDark}, Collectors: {collectorCount}"); if (isDead) { if (postLoot.Gold + postLoot.Elixir + postLoot.DarkElixir >= 0) { if (newGold + newElixir < 3000 * collectorCount) { Log.Info("[Human Barch] Surrendering because gained resources isn't enough"); break; } preLoot = postLoot; } } else { if (Attack.HaveAStar()) { Log.Info("[Human Barch] We have a star! TH Sniped!"); //Check the Delta in Resources. if (newGold + newElixir < (preLoot.Gold + preLoot.Elixir) * .05f) //Less than 5% of what is available. { //Switch the attack mode to Dead - so we get some of the collectors. Log.Info($"[Human Barch] Not much loot gained from Snipe(G:{newGold} E:{newElixir} out of G:{preLoot.Gold} E:{preLoot.Elixir}) - Try to Loot Collectors also..."); isDead = true; } else { //Halt the Attack. break; } } if (waveCounter > 10) { Log.Info("[Human Barch] Fail! TH Not Sniped! our troops died - Surrendering..."); break; } } waveCounter++; } //TODO - Can we destroy some trash buildings to get a star if we dont already have one? //Last thing Call ZapDarkElixterDrills... This uses the Clashbot settings for when to zap, and what level drills to zap. Log.Info("[Human Barch] Checking to see if we can Zap DE Drills..."); foreach (var t in ZapDarkElixirDrills()) { yield return(t); } //We broke out of the attack loop... Attack.Surrender(); }
public override IEnumerable <int> AttackRoutine() { Log.Info("[Red Line] Attack start"); var deployElements = Deploy.GetTroops(); deployElements.Extract(DeployElementType.Spell); var heroes = deployElements.Extract(u => u.IsHero); var cc = deployElements.ExtractOne(DeployId.ClanCastle); var ranged = deployElements.Extract(u => u.IsRanged); var units = deployElements.OrderBy(u => u.UnitData?.AttackType != AttackType.Tank).ToList(); var clockwisePoints = 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)) .OrderBy(point => Math.Atan2(point.X, point.Y)).ToList(); var inflatedPoints = clockwisePoints .Select( point => new PointFT((float)(point.X + (point.X / Math.Sqrt(point.DistanceSq(new PointFT(0, 0)))) * 4), (float)(point.Y + (point.Y / Math.Sqrt(point.DistanceSq(new PointFT(0, 0)))) * 4))) .ToList(); while (units.Count > 0) { Log.Info("[Red Line] Deploying melee and tanking troops"); foreach (var element in units) { foreach ( var t in Deploy.AtPoints(element, clockwisePoints.GetNth(GameGrid.RedPoints.Length / (double)element.Count).ToArray())) { yield return(t); } } units.Recount(); units.RemoveAll(unit => unit.Count < 1); } while (ranged.Count > 0) { Log.Info("[Red Line] Deploying ranged troops"); foreach (var element in ranged) { foreach ( var t in Deploy.AtPoints(element, inflatedPoints.GetNth(GameGrid.RedPoints.Length / (double)element.Count).ToArray())) { yield return(t); } } ranged.Recount(); ranged.RemoveAll(unit => unit.Count < 1); } var pt = new Container <PointFT> { Item = new PointFT((float)GameGrid.MinX, GameGrid.MinY) }; if (cc?.Count > 0 && UserSettings.UseClanTroops) { Log.Info($"[Red Line] Deploying {cc.PrettyName}"); foreach (var y in Deploy.AtPoint(cc, pt)) { yield return(y); } } if (heroes.Any()) { foreach (var hero in heroes.Where(u => u.Count > 0)) { Log.Info($"[Red Line] Deploying {hero.PrettyName}"); foreach (var t in Deploy.AtPoint(hero, pt)) { yield return(t); } } } Deploy.WatchHeroes(heroes); Log.Info("[Red Line] Deploy done"); }
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); } }