private static void DoBoss(BotMain bot, TerritoryStanding terr, SpecialUnit su) { AILog.Log("SpecialUnits", "Considering boss " + su.ID + " on " + bot.TerrString(terr.ID)); var routes = bot.Directives.Where(o => o.StartsWith("BossRoute ")).Select(o => new BossRoute(o, bot)).ToList(); var routeNexts = routes.Select(o => new { Route = o, Terr = o.NextTerr(terr.ID) }).Where(o => o.Terr.HasValue).ToList(); if (routeNexts.Count > 0) { var routeNext = routeNexts.WeightedRandom(o => o.Route.Weight); AILog.Log("SpecialUnits", routeNexts.Count + " matching routes: " + routeNexts.Select(o => o.Route.Name).JoinStrings(", ") + ", selected " + routeNext.Route); if (RandomUtility.RandomPercentage() > routeNext.Route.Chance) { AILog.Log("SpecialUnits", "Skipping boss route to " + routeNext.Terr.Value + " due to failed random chance. "); } else { AILog.Log("SpecialUnits", "Moving boss along route to " + bot.TerrString(routeNext.Terr.Value) + ". "); bot.Orders.AddAttack(terr.ID, routeNext.Terr.Value, AttackTransferEnum.AttackTransfer, 0, true, bosses: true); bot.AvoidTerritories.Add(routeNext.Terr.Value); return; } } else if (routes.Count > 0) { //Move towards the nearest route territory. If there's a tie, take the one that's furthest along in that route var terrRoutes = routes.SelectMany(r => r.Route.Select((t, i) => new { Route = r, Terr = t, Index = i })) .GroupBy(o => o.Terr) .Select(o => o.MaxSelectorOrDefault(r => r.Index)) .ToDictionary(o => o.Terr, o => o); var visited = new HashSet <TerritoryIDType>(); visited.Add(terr.ID); while (true) { var visit = visited.SelectMany(o => bot.Map.Territories[o].ConnectedTo.Keys).ToHashSet(false); if (visit.Count == 0) { throw new Exception("Never found route territory"); } var visitOnRoute = visit.Where(o => terrRoutes.ContainsKey(o)).ToList(); if (visitOnRoute.Count > 0) { var final = visitOnRoute.Select(o => terrRoutes[o]).MaxSelectorOrDefault(o => o.Index); if (RandomUtility.RandomPercentage() > final.Route.Chance) { AILog.Log("SpecialUnits", "Skipping moving boss to route due to failed random check: " + final.Route); break; } else { var move = FindPath.TryFindShortestPath(bot, terr.ID, t => t == final.Terr); AILog.Log("SpecialUnits", "Moving boss to get back to route. Moving to " + bot.TerrString(move[0]) + " to get to " + bot.TerrString(final.Terr) + " index=" + final.Index + " " + final.Route); bot.Orders.AddAttack(terr.ID, move[0], AttackTransferEnum.AttackTransfer, 0, true, bosses: true); bot.AvoidTerritories.Add(final.Terr); return; } } visited.AddRange(visit); } } var attackCandidates = bot.Map.Territories[terr.ID].ConnectedTo.Keys.Select(o => bot.Standing.Territories[o]) .Where(o => !bot.IsTeammateOrUs(o.OwnerPlayerID) && o.NumArmies.DefensePower < 300 && !bot.AvoidTerritories.Contains(o.ID)) .ToList(); if (attackCandidates.Count > 0) { var ranks = attackCandidates.ToDictionary(o => o.ID, ts => { if (bot.IsOpponent(ts.OwnerPlayerID)) { return(bot.Players[ts.OwnerPlayerID].IsAI ? 3 : 2); //prefer human player } else if (ts.OwnerPlayerID == TerritoryStanding.NeutralPlayerID) { return(1); } else { throw new Exception("Unexpected owner " + ts.OwnerPlayerID); } }); var max = ranks.Values.Max(); var to = ranks.Where(o => o.Value == max).Random().Key; AILog.Log("SpecialUnits", "Normal boss move to " + bot.TerrString(to)); bot.Orders.AddAttack(terr.ID, to, AttackTransferEnum.AttackTransfer, 0, false, bosses: true); bot.AvoidTerritories.Add(to); } else { //Surrounded by ourself or teammates. Move towards enemy var move = bot.MoveTowardsNearestBorderNonNeutralThenNeutral(terr.ID); if (move.HasValue) { AILog.Log("SpecialUnits", "Landlocked boss move to " + bot.TerrString(move.Value)); bot.Orders.AddAttack(terr.ID, move.Value, AttackTransferEnum.AttackTransfer, 0, false, bosses: true); } } }