public static bool IsNuke(JSON.Army army) { return (BattleSimulator.TotalAttackPower(army) > 4e5 && ArmyStats.CalculateTotalPopulation(army.OfType(JSON.UnitBuild.Offensive)) > 10000 && ArmyStats.CalculateTotalPopulation(army) > FullArmyPopulation); }
public async Task <IActionResult> GetAvailableBacklineDefense(short x, short y, int?maxTravelSeconds) { var currentVillages = await( from user in CurrentSets.ActiveUser join village in CurrentSets.Village on user.PlayerId equals village.PlayerId join currentVillage in CurrentSets.CurrentVillage.Include(cv => cv.ArmyAtHome).Include(cv => cv.ArmyOwned) on village.VillageId equals currentVillage.VillageId where currentVillage.ArmyAtHomeId != null && currentVillage.ArmyOwnedId != null select new { Village = village, CurrentVillage = currentVillage } ).ToListAsync(); var enemyVillages = await( from enemy in CurrentSets.EnemyTribe join player in CurrentSets.Player on enemy.EnemyTribeId equals player.TribeId join village in CurrentSets.Village on player.PlayerId equals village.PlayerId select new Coordinate { X = village.X.Value, Y = village.Y.Value } ).ToListAsync(); // Ignore current villages where < 10% of their troops are at home currentVillages = currentVillages // + 1 on army owned so we don't divide by 0 accidentally .Where(v => ArmyStats.CalculateTotalPopulation(v.CurrentVillage.ArmyAtHome) / (float)(ArmyStats.CalculateTotalPopulation(v.CurrentVillage.ArmyOwned) + 1) > 0.2f) .ToList(); var enemyMap = new Features.Spatial.Quadtree(enemyVillages); var backlineSupport = currentVillages .Where(s => !enemyMap.ContainsInRange(s.Village.X.Value, s.Village.Y.Value, 10)) .ToDictionary(s => s.Village, s => s.CurrentVillage); var planner = new Features.Planning.CommandOptionsCalculator(CurrentWorldSettings); planner.Requirements.Add(new MinimumDefenseRequirement { MinimumDefense = 10000 }.LimitTroopType(ArmyStats.DefensiveTroopTypes)); if (maxTravelSeconds != null) { planner.Requirements.Add(new MaximumTravelTimeRequirement { MaximumTime = TimeSpan.FromSeconds(maxTravelSeconds.Value) }); } var options = planner.GenerateOptions(backlineSupport, new Village { X = x, Y = y }); var villageIds = options.Select(o => o.SendFrom).Distinct().ToList(); var playerIds = await CurrentSets.Village.Where(v => villageIds.Contains(v.VillageId)).Select(v => v.PlayerId.Value).Distinct().ToListAsync(); if (playerIds.Contains(CurrentPlayerId)) { playerIds.Remove(CurrentPlayerId); } var players = await CurrentSets.Player.Where(p => playerIds.Contains(p.PlayerId)).Select(p => new { PlayerName = p.PlayerName.UrlDecode(), p.PlayerId }).ToListAsync(); return(Ok(players)); }
public NukeEstimationResult EstimateRequiredNukes(Army defendingArmy, int wallLevel, bool useArchers, int moralePercent) { defendingArmy = defendingArmy ?? new Army(); var activeDefendingArmy = new Army(defendingArmy); int numNukes = 0; float lastNukeLossRatio = 0; for (int i = 0; i < MaxSimulationIterations && !IsArmyEmpty(activeDefendingArmy) && numNukes < 50; i++) { var battleResult = SimulateAttack(DefaultNukeArmy, activeDefendingArmy, wallLevel, useArchers, moralePercent); wallLevel = battleResult.NewWallLevel; activeDefendingArmy = battleResult.DefendingArmy; ++numNukes; lastNukeLossRatio = 1.0f - ArmyStats.CalculateTotalPopulation(battleResult.AttackingArmy) / (float)ArmyStats.CalculateTotalPopulation(DefaultNukeArmy); } return(new NukeEstimationResult { NukesRequired = numNukes, LastNukeLossesPercent = lastNukeLossRatio * 100 }); }
public async Task<IActionResult> GetVillageArmy(long villageId, int? morale) { if (!await CanReadVillage(villageId)) return StatusCode(401); PreloadWorldData(); var uploadHistory = await Profile("Get user upload history", () => context.UserUploadHistory.Where(h => h.Uid == CurrentUserId).FirstOrDefaultAsync() ); var validationInfo = UploadRestrictionsValidate.ValidateInfo.FromMapRestrictions(CurrentUser, uploadHistory); List<String> needsUpdateReasons = UploadRestrictionsValidate.GetNeedsUpdateReasons(CurrentServerTime, validationInfo); if (needsUpdateReasons != null && needsUpdateReasons.Any()) { return StatusCode(423, needsUpdateReasons.Select(r => Translate(r)).ToList()); // Status code "Locked" } // Start getting village data var currentVillage = await Profile("Get current village", () => ( from cv in CurrentSets.CurrentVillage .Include(v => v.ArmyAtHome) .Include(v => v.ArmyOwned) .Include(v => v.ArmyStationed) .Include(v => v.ArmyTraveling) .Include(v => v.ArmyRecentLosses) .Include(v => v.CurrentBuilding) where cv.VillageId == villageId select cv ).FirstOrDefaultAsync()); var commandsToVillage = await Profile("Get commands to village", () => ( from command in CurrentSets.Command .Include(c => c.Army) where command.TargetVillageId == villageId where !command.IsReturning where command.LandsAt > CurrentServerTime select command ).ToListAsync()); var latestConquerTimestamp = await Profile("Get latest conquer", () => ( from conquer in CurrentSets.Conquer where conquer.VillageId == villageId orderby conquer.UnixTimestamp descending select conquer.UnixTimestamp ).FirstOrDefaultAsync()); var jsonData = new JSON.VillageData(); // Return empty data if no data is available for the village if (currentVillage == null) return Ok(jsonData); Profile("Populate JSON data", () => { if (currentVillage.ArmyOwned?.LastUpdated != null) { jsonData.OwnedArmy = ArmyConvert.ArmyToJson(currentVillage.ArmyOwned); jsonData.OwnedArmySeenAt = currentVillage.ArmyOwned.LastUpdated; } if (currentVillage.ArmyRecentLosses?.LastUpdated != null) { jsonData.RecentlyLostArmy = ArmyConvert.ArmyToJson(currentVillage.ArmyRecentLosses); jsonData.RecentlyLostArmySeenAt = currentVillage.ArmyRecentLosses.LastUpdated; } if (currentVillage.ArmyStationed?.LastUpdated != null) { jsonData.StationedArmy = ArmyConvert.ArmyToJson(currentVillage.ArmyStationed); jsonData.StationedSeenAt = currentVillage.ArmyStationed.LastUpdated; } if (currentVillage.ArmyTraveling?.LastUpdated != null) { jsonData.TravelingArmy = ArmyConvert.ArmyToJson(currentVillage.ArmyTraveling); jsonData.TravelingSeenAt = currentVillage.ArmyTraveling.LastUpdated; } if (currentVillage.ArmyAtHome?.LastUpdated != null) { jsonData.AtHomeArmy = ArmyConvert.ArmyToJson(currentVillage.ArmyAtHome); jsonData.AtHomeSeenAt = currentVillage.ArmyAtHome.LastUpdated; } jsonData.LastLoyalty = currentVillage.Loyalty; jsonData.LastLoyaltySeenAt = currentVillage.LoyaltyLastUpdated; if (currentVillage.Loyalty != null) { var loyaltyCalculator = new LoyaltyCalculator(CurrentWorldSettings.GameSpeed); jsonData.PossibleLoyalty = loyaltyCalculator.PossibleLoyalty(currentVillage.Loyalty.Value, CurrentServerTime - currentVillage.LoyaltyLastUpdated.Value); } jsonData.LastBuildings = BuildingConvert.CurrentBuildingToJson(currentVillage.CurrentBuilding); jsonData.LastBuildingsSeenAt = currentVillage.CurrentBuilding?.LastUpdated; if (currentVillage.CurrentBuilding?.LastUpdated != null) { var constructionCalculator = new ConstructionCalculator(); jsonData.PossibleBuildings = constructionCalculator.CalculatePossibleBuildings(jsonData.LastBuildings, CurrentServerTime - currentVillage.CurrentBuilding.LastUpdated.Value); } if (currentVillage.ArmyStationed?.LastUpdated != null) { var battleSimulator = new BattleSimulator(); short wallLevel = currentVillage.CurrentBuilding?.Wall ?? 20; short hqLevel = currentVillage.CurrentBuilding?.Main ?? 20; if (currentVillage.CurrentBuilding != null) wallLevel += new ConstructionCalculator().CalculateLevelsInTimeSpan(BuildingType.Wall, hqLevel, wallLevel, CurrentServerTime - currentVillage.CurrentBuilding.LastUpdated.Value); var nukeEstimation = battleSimulator.EstimateRequiredNukes(jsonData.StationedArmy, wallLevel, CurrentWorldSettings.ArchersEnabled, morale ?? 100); jsonData.NukesRequired = nukeEstimation.NukesRequired; jsonData.LastNukeLossPercent = (int)(nukeEstimation.LastNukeLossesPercent); } // Might have CurrentArmy entries but they're just empty/null - not based on any report data if (jsonData.OwnedArmy != null && (jsonData.OwnedArmySeenAt == null || jsonData.OwnedArmy.Count == 0)) { jsonData.OwnedArmy = null; jsonData.OwnedArmySeenAt = null; } if (jsonData.StationedArmy != null && (jsonData.StationedSeenAt == null || jsonData.StationedArmy.Count == 0)) { jsonData.StationedArmy = null; jsonData.StationedSeenAt = null; } if (jsonData.TravelingArmy != null && (jsonData.TravelingSeenAt == null || jsonData.TravelingArmy.Count == 0)) { jsonData.TravelingArmy = null; jsonData.TravelingSeenAt = null; } if (jsonData.RecentlyLostArmy != null && (jsonData.RecentlyLostArmySeenAt == null || jsonData.RecentlyLostArmy.Count == 0)) { jsonData.RecentlyLostArmy = null; jsonData.RecentlyLostArmySeenAt = null; } var armyCalculator = new RecruitmentCalculator(2, jsonData.LastBuildings); DateTime? localArmyLastSeenAt = null; int? availableArmyPopulation = null; if (jsonData.StationedArmy != null) { localArmyLastSeenAt = jsonData.StationedSeenAt.Value; var existingPop = ArmyStats.CalculateTotalPopulation(jsonData.StationedArmy); availableArmyPopulation = Math.Max(0, armyCalculator.MaxPopulation - existingPop); } var conquerTime = DateTimeOffset.FromUnixTimeMilliseconds(latestConquerTimestamp).UtcDateTime; bool useConquer = false; if (localArmyLastSeenAt == null) useConquer = true; else useConquer = conquerTime > localArmyLastSeenAt.Value; if (useConquer) { localArmyLastSeenAt = conquerTime; availableArmyPopulation = armyCalculator.MaxPopulation; } // Add recruitment estimations if (localArmyLastSeenAt != null) { var timeSinceSeen = CurrentServerTime - localArmyLastSeenAt.Value; armyCalculator.MaxPopulation = availableArmyPopulation.Value; // No point in estimating troops if there's been 2 weeks since we saw stationed troops if (timeSinceSeen.TotalDays < 14) { jsonData.PossibleRecruitedOffensiveArmy = armyCalculator.CalculatePossibleOffenseRecruitment(timeSinceSeen); jsonData.PossibleRecruitedDefensiveArmy = armyCalculator.CalculatePossibleDefenseRecruitment(timeSinceSeen); } } // Add command summaries jsonData.DVs = new Dictionary<long, int>(); jsonData.Fakes = new List<long>(); jsonData.Nukes = new List<long>(); jsonData.Nobles = new List<long>(); jsonData.Players = commandsToVillage.Select(c => c.SourcePlayerId).Distinct().ToList(); foreach (var command in commandsToVillage.Where(c => c.Army != null)) { var army = ArmyConvert.ArmyToJson(command.Army); var offensivePop = ArmyStats.CalculateTotalPopulation(army.OfType(JSON.UnitBuild.Offensive)); var defensivePop = ArmyStats.CalculateTotalPopulation(army.OfType(JSON.UnitBuild.Defensive)); var isNoble = command.Army.Snob > 0 && command.IsAttack; bool isFake = false; bool isNuke = false; if (!army.Values.Any(cnt => cnt > 1) && !isNoble) { isFake = true; } else if (command.IsAttack && offensivePop > 10000) { isNuke = true; } if (isFake) jsonData.Fakes.Add(command.CommandId); else if (isNuke) jsonData.Nukes.Add(command.CommandId); else if (defensivePop > 3000 && !command.IsAttack) jsonData.DVs.Add(command.CommandId, defensivePop); if (isNoble) jsonData.Nobles.Add(command.CommandId); } }); return Ok(jsonData); }
public async Task<IActionResult> GetMapTags(int x, int y, int width, int height) { var uploadHistory = await context.UserUploadHistory.Where(u => u.Uid == CurrentUserId).FirstOrDefaultAsync(); var validationInfo = UploadRestrictionsValidate.ValidateInfo.FromMapRestrictions(CurrentUser, uploadHistory); var needsUpdateReasons = UploadRestrictionsValidate.GetNeedsUpdateReasons(CurrentServerTime, validationInfo); if (needsUpdateReasons != null && needsUpdateReasons.Any()) { return StatusCode(423, needsUpdateReasons.Select(r => Translate(r)).ToList()); // Status code "Locked" } var vaultTribes = await Profile("Get tribe IDs", () => ( from user in CurrentSets.User join player in CurrentSets.Player on user.PlayerId equals player.PlayerId where player.TribeId != null && user.Enabled select player.TribeId.Value ).Distinct().ToListAsync()); var enemyTribes = await CurrentSets.EnemyTribe.Select(et => et.EnemyTribeId).ToListAsync(); var villageData = await Profile("Get village data", () => ( from currentVillage in CurrentSets.CurrentVillage .Include(cv => cv.ArmyOwned) .Include(cv => cv.ArmyTraveling) .Include(cv => cv.ArmyStationed) .Include(cv => cv.CurrentBuilding) join village in CurrentSets.Village on currentVillage.VillageId equals village.VillageId join player in CurrentSets.Player on village.PlayerId equals player.PlayerId where CurrentUserIsAdmin || player.TribeId == null || !vaultTribes.Contains(player.TribeId.Value) // Right now we're not doing lazy-loading of data in the script, so this is just a potentially-costly // and pointless calculation. //where village.X >= x && village.Y >= y && village.X <= x + width && village.Y <= y + height select new { CurrentVillage = currentVillage, player.PlayerId, player.TribeId } ).ToListAsync()); var ownVillageData = await Profile("Get own village data", () => ( from village in CurrentSets.Village join currentVillage in CurrentSets.CurrentVillage on village.VillageId equals currentVillage.VillageId where village.PlayerId == CurrentPlayerId select new { Village = village, currentVillage.ArmyAtHome } ).ToListAsync()); var validVillages = villageData.Where(vd => vd.PlayerId != CurrentPlayerId).ToList(); var validVillageIds = villageData.Select(d => d.CurrentVillage.VillageId).ToList(); var returningCommandData = await Profile("Get returning command data", () => ( from command in CurrentSets.Command where command.ReturnsAt > CurrentServerTime && command.ArmyId != null && command.IsReturning select new { command.SourceVillageId, command.TargetVillageId, command.Army } ).ToListAsync()); var sendingCommandData = await Profile("Get sent command data", () => ( from command in CurrentSets.Command where command.ReturnsAt > CurrentServerTime && command.ArmyId != null && !command.IsReturning select new { command.SourceVillageId, command.TargetVillageId, command.Army } ).ToListAsync()); var returningCommandsBySourceVillageId = validVillageIds.ToDictionary(id => id, id => new List<Scaffold.CommandArmy>()); foreach (var command in returningCommandData) { if (returningCommandsBySourceVillageId.ContainsKey(command.SourceVillageId)) returningCommandsBySourceVillageId[command.SourceVillageId].Add(command.Army); } var commandsByTargetVillageId = validVillageIds.ToDictionary(id => id, id => new List<Scaffold.CommandArmy>()); foreach (var command in sendingCommandData) { if (commandsByTargetVillageId.ContainsKey(command.TargetVillageId)) commandsByTargetVillageId[command.TargetVillageId].Add(command.Army); } var result = new Dictionary<long, JSON.VillageTags>(); var tribeIds = validVillages.Select(vv => vv.TribeId).Where(t => t != null).Select(t => t.Value).Distinct(); var tribeNames = await Profile("Get tribe names", () => ( from tribe in CurrentSets.Ally where tribeIds.Contains(tribe.TribeId) select new { tribe.Tag, tribe.TribeId } ).ToListAsync()); var tribeNamesById = tribeIds.ToDictionary(tid => tid, tid => tribeNames.First(tn => tn.TribeId == tid).Tag.UrlDecode()); Profile("Generate JSON tags", () => { foreach (var data in validVillages) { var village = data.CurrentVillage; var tag = new JSON.VillageTags(); tag.TribeName = data.TribeId == null ? null : tribeNamesById[data.TribeId.Value]; tag.IsEnemyTribe = data.TribeId == null ? false : enemyTribes.Contains(data.TribeId.Value); tag.WallLevel = data.CurrentVillage.CurrentBuilding?.Wall; tag.WallLevelSeenAt = data.CurrentVillage.CurrentBuilding?.LastUpdated; tag.WatchtowerLevel = data.CurrentVillage.CurrentBuilding?.Watchtower; tag.WatchtowerSeenAt = data.CurrentVillage.CurrentBuilding?.LastUpdated; bool IsNuke(Scaffold.IScaffoldArmy army) { var jsonArmy = ArmyConvert.ArmyToJson(army); if (BattleSimulator.TotalAttackPower(jsonArmy) < 3.5e5) return false; if (ArmyStats.CalculateTotalPopulation(jsonArmy, TroopType.Axe, TroopType.Light, TroopType.Marcher) < 4000) return false; // Check HC nuke if (army.Light < 100) { return ArmyStats.CalculateTotalPopulation(jsonArmy, TroopType.Axe, TroopType.Heavy, TroopType.Marcher) > 15000 && army.Axe > army.Heavy; } else { // 13k pop, ie 5k axe, 2k lc return ArmyStats.CalculateTotalPopulation(jsonArmy, TroopType.Axe, TroopType.Light, TroopType.Marcher) > 13000; } } tag.ReturningTroopsPopulation = returningCommandsBySourceVillageId[data.CurrentVillage.VillageId].Sum((army) => ArmyStats.CalculateTotalPopulation(ArmyConvert.ArmyToJson(army))); tag.NumTargettingNukes = commandsByTargetVillageId[data.CurrentVillage.VillageId].Count(army => IsNuke(army)); if (village.ArmyStationed?.LastUpdated != null) { // 1 DV is approx. 1.7m total defense power var stationed = village.ArmyStationed; var defensePower = BattleSimulator.TotalDefensePower(ArmyConvert.ArmyToJson(stationed)); tag.IsStacked = defensePower > 1.7e6; tag.StackDVs = defensePower / (float)1.7e6; tag.StackSeenAt = village.ArmyStationed.LastUpdated; } var validArmies = new[] { village.ArmyOwned, village.ArmyTraveling, village.ArmyStationed }.Where(a => a?.LastUpdated != null && !ArmyConvert.ArmyToJson(a).IsEmpty()).ToList(); var nukeArmy = ( from army in validArmies where IsNuke(army) orderby army.LastUpdated.Value descending select army ).FirstOrDefault(); var nobleArmy = ( from army in validArmies where army.Snob > 0 orderby army.LastUpdated.Value descending select army ).FirstOrDefault(); if (nukeArmy != null) { tag.HasNuke = true; tag.NukeSeenAt = nukeArmy.LastUpdated; } if (nobleArmy != null) { tag.HasNobles = true; tag.NoblesSeenAt = nobleArmy.LastUpdated; } result.Add(village.VillageId, tag); } }); return Ok(result); }
public async Task <IActionResult> GetTroopsSummary(int fangMinCats, int fangMaxPop) { // Dear jesus this is such a mess context.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking; if (!CurrentUserIsAdmin) { var authRecord = MakeFailedAuthRecord("User is not admin"); context.Add(authRecord); await context.SaveChangesAsync(); return(Unauthorized()); } // This is a mess because of different classes for Player, CurrentPlayer, etc // Get all CurrentVillages from the user's tribe - list of (Player, CurrentVillage) // (This returns a lot of data and will be slow) var tribeVillages = await( from player in CurrentSets.Player join user in CurrentSets.User on player.PlayerId equals user.PlayerId join village in CurrentSets.Village on player.PlayerId equals village.PlayerId join currentVillage in CurrentSets.CurrentVillage on village.VillageId equals currentVillage.VillageId into currentVillage where user.Enabled && !user.IsReadOnly where player.TribeId == CurrentTribeId || !Configuration.Security.RestrictAccessWithinTribes select new { player, villageId = village.VillageId, currentVillage = currentVillage.FirstOrDefault(), X = village.X.Value, Y = village.Y.Value } ).ToListAsync(); var currentPlayers = await( // Get all CurrentPlayer data for the user's tribe (separate from global 'Player' table // so we can also output stats for players that haven't uploaded anything yet) from currentPlayer in CurrentSets.CurrentPlayer join player in CurrentSets.Player on currentPlayer.PlayerId equals player.PlayerId join user in CurrentSets.User on player.PlayerId equals user.PlayerId where user.Enabled && !user.IsReadOnly where player.TribeId == CurrentTribeId || !Configuration.Security.RestrictAccessWithinTribes select currentPlayer ).ToListAsync(); var uploadHistory = await( // Get user upload history from history in context.UserUploadHistory join user in CurrentSets.User on history.Uid equals user.Uid join player in CurrentSets.Player on user.PlayerId equals player.PlayerId where player.TribeId == CurrentTribeId || !Configuration.Security.RestrictAccessWithinTribes where user.Enabled && !user.IsReadOnly select new { playerId = player.PlayerId, history } ).ToListAsync(); var enemyVillages = await( // Get enemy villages from tribe in CurrentSets.EnemyTribe join player in CurrentSets.Player on tribe.EnemyTribeId equals player.TribeId join village in CurrentSets.Village on player.PlayerId equals village.PlayerId select new { village.VillageId, X = village.X.Value, Y = village.Y.Value } ).ToListAsync(); // Need to load armies separately since `join into` doesn't work right with .Include() var armyIds = tribeVillages .Where(v => v.currentVillage != null) .SelectMany(v => new[] { v.currentVillage.ArmyAtHomeId, v.currentVillage.ArmyOwnedId, v.currentVillage.ArmyRecentLossesId, v.currentVillage.ArmyStationedId, v.currentVillage.ArmySupportingId, v.currentVillage.ArmyTravelingId }) .Where(id => id != null) .Select(id => id.Value) .ToList(); var allArmies = await context.CurrentArmy.Where(a => armyIds.Contains(a.ArmyId)).ToDictionaryAsync(a => a.ArmyId, a => a); foreach (var village in tribeVillages.Where(v => v.currentVillage != null)) { Scaffold.CurrentArmy FindArmy(long?armyId) => armyId == null ? null : allArmies.GetValueOrDefault(armyId.Value); var cv = village.currentVillage; cv.ArmyAtHome = FindArmy(cv.ArmyAtHomeId); cv.ArmyOwned = FindArmy(cv.ArmyOwnedId); cv.ArmyRecentLosses = FindArmy(cv.ArmyRecentLossesId); cv.ArmyStationed = FindArmy(cv.ArmyStationedId); cv.ArmySupporting = FindArmy(cv.ArmySupportingId); cv.ArmyTraveling = FindArmy(cv.ArmyTravelingId); } var currentPlayerIds = currentPlayers.Select(p => p.PlayerId).ToList(); var villageIds = tribeVillages.Select(v => v.villageId).Distinct().ToList(); var attackedVillageIds = await Profile("Get incomings", () => ( from command in CurrentSets.Command where villageIds.Contains(command.TargetVillageId) && command.IsAttack && command.LandsAt > CurrentServerTime && !command.IsReturning where !currentPlayerIds.Contains(command.SourcePlayerId) select command.TargetVillageId ).ToListAsync()); var attackCommands = await Profile("Get attack details", () => ( from command in CurrentSets.Command.Include(c => c.Army) where villageIds.Contains(command.SourceVillageId) && command.IsAttack && command.LandsAt > CurrentServerTime where command.TargetPlayerId != null select new { command.SourceVillageId, command.Army } ).ToListAsync()); var attackingVillageIds = attackCommands.Select(c => c.SourceVillageId).ToList(); var tribeIds = tribeVillages.Select(tv => tv.player.TribeId) .Where(tid => tid != null) .Distinct() .Select(tid => tid.Value) .ToList(); // Collect villages grouped by owner var villagesByPlayer = tribeVillages .Select(v => v.player) .Distinct() .ToDictionary( p => p, p => tribeVillages.Where(v => v.player == p) .Select(tv => tv.currentVillage) .Where(cv => cv != null) .ToList() ); var villageIdsByPlayer = villagesByPlayer.ToDictionary( kvp => kvp.Key, kvp => kvp.Value.Select(v => v.VillageId).ToList() ); var uploadHistoryByPlayer = uploadHistory .Select(h => h.playerId) .Distinct() .ToDictionary( p => p, p => uploadHistory.Where(h => h.playerId == p) .Select(h => h.history) .FirstOrDefault() ); // Get all support data for the tribe // 'villageIds' tends to be large, so this will be a slow query var villagesSupport = await( from support in CurrentSets.CurrentVillageSupport .Include(s => s.SupportingArmy) where villageIds.Contains(support.SourceVillageId) select support ).ToListAsync(); // Get support data by player Id, and sorted by target tribe ID var playersById = tribeVillages.Select(tv => tv.player).Distinct().ToDictionary(p => p.PlayerId, p => p); var tribeIdsByVillage = tribeVillages.ToDictionary( v => v.villageId, v => v.player.TribeId ?? -1 ); // Get tribes being supported that are not from vault var nonTribeVillageIds = villagesSupport.Select(s => s.TargetVillageId).Distinct().Except(villageIds).ToList(); var nonTribeTargetTribesByVillageId = await( from village in CurrentSets.Village join player in CurrentSets.Player on village.PlayerId equals player.PlayerId join ally in CurrentSets.Ally on player.TribeId equals ally.TribeId where nonTribeVillageIds.Contains(village.VillageId) select new { village.VillageId, ally.TribeId } ).ToDictionaryAsync(d => d.VillageId, d => d.TribeId); foreach (var entry in nonTribeTargetTribesByVillageId) { tribeIdsByVillage.Add(entry.Key, entry.Value); } tribeIds = tribeIds.Concat(nonTribeTargetTribesByVillageId.Values.Distinct()).Distinct().ToList(); var villagesSupportByPlayerId = new Dictionary <long, List <Scaffold.CurrentVillageSupport> >(); var villagesSupportByPlayerIdByTargetTribeId = new Dictionary <long, Dictionary <long, List <Scaffold.CurrentVillageSupport> > >(); // Only check support with players that have registered villas foreach (var player in currentPlayers.Where(p => playersById.ContainsKey(p.PlayerId))) { var supportFromPlayer = villagesSupport.Where( s => villageIdsByPlayer[playersById[player.PlayerId]].Contains(s.SourceVillageId) ).ToList(); villagesSupportByPlayerId.Add(player.PlayerId, supportFromPlayer); var supportByTribe = tribeIds.ToDictionary(tid => tid, _ => new List <Scaffold.CurrentVillageSupport>()); supportByTribe.Add(-1, new List <Scaffold.CurrentVillageSupport>()); foreach (var support in supportFromPlayer) { var targetTribeId = tribeIdsByVillage.GetValueOrDefault(support.TargetVillageId, -1); supportByTribe[targetTribeId].Add(support); } villagesSupportByPlayerIdByTargetTribeId.Add(player.PlayerId, supportByTribe); } var numIncomingsByPlayer = new Dictionary <long, int>(); var numAttacksByPlayer = new Dictionary <long, int>(); var numAttackingFangsByPlayer = new Dictionary <long, int>(); var villageOwnerIdById = tribeVillages.ToDictionary(v => v.villageId, v => v.player.PlayerId); foreach (var target in attackedVillageIds) { var playerId = villageOwnerIdById[target]; if (!numIncomingsByPlayer.ContainsKey(playerId)) { numIncomingsByPlayer[playerId] = 0; } numIncomingsByPlayer[playerId]++; } foreach (var source in attackingVillageIds) { var playerId = villageOwnerIdById[source]; if (!numAttacksByPlayer.ContainsKey(playerId)) { numAttacksByPlayer[playerId] = 0; } numAttacksByPlayer[playerId]++; } bool IsFang(JSON.Army army, bool ignorePop = false) => army != null && army.ContainsKey(JSON.TroopType.Catapult) && army[JSON.TroopType.Catapult] >= fangMinCats && (ignorePop || ArmyStats.CalculateTotalPopulation(army, ArmyStats.OffensiveTroopTypes) <= fangMaxPop); foreach (var command in attackCommands) { var playerId = villageOwnerIdById[command.SourceVillageId]; if (!numAttackingFangsByPlayer.ContainsKey(playerId)) { numAttackingFangsByPlayer[playerId] = 0; } if (IsFang(command.Army)) { numAttackingFangsByPlayer[playerId]++; } } var villagesNearEnemy = new HashSet <long>(); foreach (var village in tribeVillages) { var nearbyEnemyVillage = enemyVillages.FirstOrDefault(v => { var distance = Model.Coordinate.Distance(v.X, v.Y, village.X, village.Y); return(distance < 10); }); if (nearbyEnemyVillage != null) { villagesNearEnemy.Add(village.villageId); } } var maxNoblesByPlayer = currentPlayers.ToDictionary(p => p.PlayerId, p => p.CurrentPossibleNobles); // Get tribe labels var tribeNames = await( from tribe in CurrentSets.Ally where tribeIds.Contains(tribe.TribeId) select new { tribe.Tag, tribe.TribeId } ).ToListAsync(); var tribeNamesById = tribeNames.ToDictionary(tn => tn.TribeId, tn => tn.Tag.UrlDecode()); var jsonData = new List <JSON.PlayerSummary>(); foreach (var kvp in villagesByPlayer.OrderBy(kvp => kvp.Key.TribeId).ThenBy(kvp => kvp.Key.PlayerName)) { var player = kvp.Key; String playerName = player.PlayerName; String tribeName = tribeNamesById.GetValueOrDefault(player.TribeId ?? -1); var playerVillages = kvp.Value; var playerHistory = uploadHistoryByPlayer.GetValueOrDefault(player.PlayerId); var playerSummary = new JSON.PlayerSummary { PlayerName = playerName.UrlDecode(), PlayerId = player.PlayerId, TribeName = tribeName, UploadedAt = playerHistory?.LastUploadedTroopsAt ?? new DateTime(), UploadedReportsAt = playerHistory?.LastUploadedReportsAt ?? new DateTime(), UploadedIncomingsAt = playerHistory?.LastUploadedIncomingsAt ?? new DateTime(), UploadedCommandsAt = playerHistory?.LastUploadedCommandsAt ?? new DateTime(), NumNobles = playerVillages.Select(v => v.ArmyOwned?.Snob ?? 0).Sum(), NumIncomings = numIncomingsByPlayer.GetValueOrDefault(player.PlayerId, 0), NumAttackCommands = numAttacksByPlayer.GetValueOrDefault(player.PlayerId, 0), FangsTraveling = numAttackingFangsByPlayer.GetValueOrDefault(player.PlayerId, 0) }; playerSummary.UploadAge = CurrentServerTime - playerSummary.UploadedAt; if (maxNoblesByPlayer.ContainsKey(player.PlayerId)) { playerSummary.MaxPossibleNobles = maxNoblesByPlayer[player.PlayerId]; } // General army data foreach (var village in playerVillages.Where(v => v.ArmyOwned != null && v.ArmyTraveling != null && v.ArmyAtHome != null)) { var armyOwned = ArmyConvert.ArmyToJson(village.ArmyOwned); var armyTraveling = ArmyConvert.ArmyToJson(village.ArmyTraveling); var armyAtHome = ArmyConvert.ArmyToJson(village.ArmyAtHome); if (IsFang(armyAtHome, true) && !ArmyStats.IsNuke(armyOwned, 0.75)) { playerSummary.FangsOwned++; } if (ArmyStats.IsOffensive(village.ArmyOwned)) { playerSummary.NumOffensiveVillages++; var offensivePower = BattleSimulator.TotalAttackPower(armyOwned); if (ArmyStats.IsNuke(armyOwned)) { playerSummary.NukesOwned++; } else if (ArmyStats.IsNuke(armyOwned, 0.75)) { playerSummary.ThreeQuarterNukesOwned++; } else if (ArmyStats.IsNuke(armyOwned, 0.5)) { playerSummary.HalfNukesOwned++; } else if (ArmyStats.IsNuke(armyOwned, 0.25)) { playerSummary.QuarterNukesOwned++; } if (ArmyStats.IsNuke(armyTraveling)) { playerSummary.NukesTraveling++; } else if (IsFang(armyTraveling)) { playerSummary.FangsTraveling++; } } else { playerSummary.NumDefensiveVillages++; var ownedDefensivePower = BattleSimulator.TotalDefensePower(armyOwned); var atHomeDefensivePower = BattleSimulator.TotalDefensePower(armyAtHome); var travelingDefensivePower = BattleSimulator.TotalDefensePower(armyTraveling); playerSummary.DVsAtHome += atHomeDefensivePower / (float)ArmyStats.FullDVDefensivePower; if (!villagesNearEnemy.Contains(village.VillageId)) { playerSummary.DVsAtHomeBackline += atHomeDefensivePower / (float)ArmyStats.FullDVDefensivePower; } playerSummary.DVsOwned += ownedDefensivePower / (float)ArmyStats.FullDVDefensivePower; playerSummary.DVsTraveling += travelingDefensivePower / (float)ArmyStats.FullDVDefensivePower; } } // Support data var playerSupport = villagesSupportByPlayerId.GetValueOrDefault(player.PlayerId); if (playerSupport != null) { // Support where the target is one of the players' own villages foreach (var support in playerSupport.Where(s => playerVillages.Any(v => v.VillageId == s.TargetVillageId))) { playerSummary.DVsSupportingSelf += BattleSimulator.TotalDefensePower(support.SupportingArmy) / (float)ArmyStats.FullDVDefensivePower; } // Support where the target isn't any of the players' own villages foreach (var support in playerSupport.Where(s => playerVillages.All(v => v.VillageId != s.TargetVillageId))) { playerSummary.DVsSupportingOthers += BattleSimulator.TotalDefensePower(support.SupportingArmy) / (float)ArmyStats.FullDVDefensivePower; } playerSummary.SupportPopulationByTargetTribe = new Dictionary <string, int>(); foreach (var(tribeId, supportToTribe) in villagesSupportByPlayerIdByTargetTribeId[player.PlayerId]) { var supportedTribeName = tribeNamesById.GetValueOrDefault(tribeId, Translate("UNKNOWN")); var totalSupportPopulation = 0; foreach (var support in supportToTribe) { totalSupportPopulation += ArmyStats.CalculateTotalPopulation(ArmyConvert.ArmyToJson(support.SupportingArmy)); } playerSummary.SupportPopulationByTargetTribe.Add(supportedTribeName, totalSupportPopulation); } } jsonData.Add(playerSummary); } return(Ok(jsonData)); }
public bool MeetsRequirement(decimal worldSpeed, decimal travelSpeed, Coordinate source, Coordinate target, Army army) { return(ArmyStats.CalculateTotalPopulation(army) >= MinimumPopulation); }
public async Task <IActionResult> GetSuggestedActions() { PreloadWorldData(); PreloadTranslationData(); var serverTime = CurrentServerTime; var twoDaysAgo = serverTime - TimeSpan.FromDays(2); var twoDaysAgoTimestamp = new DateTimeOffset(twoDaysAgo).ToUnixTimeSeconds(); var ownVillageData = await( from village in CurrentSets.Village join currentVillage in CurrentSets.CurrentVillage on village.VillageId equals currentVillage.VillageId where village.PlayerId == CurrentPlayerId select new { X = village.X.Value, Y = village.Y.Value, village.VillageId, VillageName = village.VillageName.UrlDecode(), currentVillage.ArmyAtHome, currentVillage.ArmyStationed } ).ToListAsync(); var vaultPlayerIds = await CurrentSets.ActiveUser.Select(u => u.PlayerId).ToListAsync(); var ownVillageMap = new Features.Spatial.Quadtree(ownVillageData.Select(v => new Coordinate { X = v.X, Y = v.Y })); async Task <object> GetRecapSuggestions(CurrentContextDbSets CurrentSets) { var capturedVillages = await( from conquer in CurrentSets.Conquer join sourcePlayer in CurrentSets.ActiveUser on conquer.OldOwner equals sourcePlayer.PlayerId join village in CurrentSets.Village on conquer.VillageId equals village.VillageId where conquer.UnixTimestamp > twoDaysAgoTimestamp where conquer.NewOwner == null || !vaultPlayerIds.Contains(conquer.NewOwner.Value) where conquer.NewOwner == village.PlayerId select new { X = village.X.Value, Y = village.Y.Value, VillageId = conquer.VillageId, village.VillageName, conquer.OldOwner, conquer.NewOwner, OccurredAt = DateTimeOffset.FromUnixTimeSeconds(conquer.UnixTimestamp).UtcDateTime } ).ToListAsync(); capturedVillages = capturedVillages .GroupBy(v => v.VillageId) .Select(g => g.OrderByDescending(v => v.OccurredAt).First()) .ToList(); var villageIds = capturedVillages.Select(v => v.VillageId).ToList(); var noblesToVillages = await( from user in CurrentSets.ActiveUser join command in CurrentSets.Command on user.PlayerId equals command.SourcePlayerId where villageIds.Contains(command.TargetVillageId) where command.ArmyId != null && command.Army.Snob > 0 where command.LandsAt > serverTime select command ).ToListAsync(); var noblesToVillagesById = noblesToVillages.GroupBy(v => v.TargetVillageId).ToDictionary(g => g.Key, g => g.Count()); var relevantPlayerIds = capturedVillages .Select(v => v.OldOwner.Value) .Concat(capturedVillages.Where(v => v.NewOwner != null).Select(v => v.NewOwner.Value)) .Distinct() .ToList(); var playerNamesById = await CurrentSets.Player.Where(p => relevantPlayerIds.Contains(p.PlayerId)) .ToDictionaryAsync(p => p.PlayerId, p => p.PlayerName); var loyaltyCalculator = new Features.Simulation.LoyaltyCalculator(CurrentWorldSettings.GameSpeed); var possibleLoyalties = capturedVillages.ToDictionary( v => v.VillageId, v => loyaltyCalculator.PossibleLoyalty(25, serverTime - v.OccurredAt) ); var tlNONE = await TranslateAsync("NONE"); return(capturedVillages .Where(v => possibleLoyalties[v.VillageId] < 100) .Where(v => noblesToVillagesById.GetValueOrDefault(v.VillageId, 0) * CurrentWorldSettings.NoblemanLoyaltyMax < possibleLoyalties[v.VillageId]) .Select(v => new { v.OccurredAt, v.X, v.Y, v.VillageId, VillageName = v.VillageName.UrlDecode(), OldOwnerId = v.OldOwner, NewOwnerId = v.NewOwner, OldOwnerName = playerNamesById.GetValueOrDefault(v.OldOwner ?? -1, tlNONE).UrlDecode(), NewOwnerName = playerNamesById.GetValueOrDefault(v.NewOwner ?? -1, tlNONE).UrlDecode(), IsNearby = ownVillageMap.ContainsInRange(v.X, v.Y, 5), Loyalty = possibleLoyalties[v.VillageId] }) .OrderBy(v => v.Loyalty) .ToList()); } async Task <object> GetSnipeSuggestions(CurrentContextDbSets CurrentSets) { var incomingNobles = await( from user in CurrentSets.ActiveUser join command in CurrentSets.Command on user.PlayerId equals command.TargetPlayerId where command.IsAttack where command.TroopType == "snob" where command.LandsAt > serverTime where !command.IsReturning select new { command.TargetVillageId, command.LandsAt } ).ToListAsync(); var incomingTrains = incomingNobles .GroupBy(n => n.TargetVillageId) .ToDictionary( g => g.Key, g => g .OrderBy(n => n.LandsAt) .GroupWhile((prev, curr) => prev.LandsAt - curr.LandsAt < TimeSpan.FromSeconds(1)) .Select(t => { var train = t.ToList(); return(new { train.First().LandsAt, Range = train.Last().LandsAt - train.First().LandsAt, Train = train }); }) .ToList() ); var targetVillageIds = incomingTrains.Keys.ToList(); var targetVillageInfo = await( from village in CurrentSets.Village join currentVillage in CurrentSets.CurrentVillage.Include(cv => cv.ArmyStationed) on village.VillageId equals currentVillage.VillageId where targetVillageIds.Contains(village.VillageId) select new { Village = village, CurrentVillage = currentVillage } ).ToListAsync(); var ownVillages = await( from village in CurrentSets.Village join currentVillage in CurrentSets.CurrentVillage.Include(cv => cv.ArmyAtHome) on village.VillageId equals currentVillage.VillageId where village.PlayerId == CurrentPlayerId where currentVillage.ArmyAtHomeId != null select new { Village = village, CurrentVillage = currentVillage } ).ToDictionaryAsync(d => d.Village, d => d.CurrentVillage); var planner = new Features.Planning.CommandOptionsCalculator(CurrentWorldSettings); var timeRequirement = new MaximumTravelTimeRequirement(); planner.Requirements.Add(new MinimumDefenseRequirement { MinimumDefense = 10000 }.LimitTroopType(ArmyStats.DefensiveTroopTypes)); planner.Requirements.Add(timeRequirement); return(targetVillageInfo // Don't bother sniping stacked villas .Where(info => { var defPop = ArmyStats.CalculateTotalPopulation(info.CurrentVillage.ArmyStationed, ArmyStats.DefensiveTroopTypes); var numDVsStationed = defPop / (float)Features.CommandClassification.Utils.FullArmyPopulation; return numDVsStationed < 2; }) // Make a plan for each train .SelectMany(info => incomingTrains[info.Village.VillageId].Select(train => { timeRequirement.MaximumTime = train.LandsAt - serverTime; var plan = planner.GenerateOptions(ownVillages, info.Village).Take(100).ToList(); return new { info.Village, Train = train, Plan = plan }; })) // Ignore plans without any instructions .Where(info => info.Plan.Count > 0) // For each train and target village, return a plan with info on the villa that needs to be sniped .Select(info => new { Plan = info.Plan, Train = info.Train.Train, LandsAt = info.Train.LandsAt, TargetVillage = new { Id = info.Village.VillageId, Name = info.Village.VillageName.UrlDecode(), X = info.Village.X.Value, Y = info.Village.Y.Value } }) .OrderBy(info => info.LandsAt) .ToList()); } async Task <object> GetStackSuggestions(CurrentContextDbSets CurrentSets) { var enemyVillages = await( from enemy in CurrentSets.EnemyTribe join player in CurrentSets.Player on enemy.EnemyTribeId equals player.TribeId join village in CurrentSets.Village on player.PlayerId equals village.PlayerId select new Coordinate { X = village.X.Value, Y = village.Y.Value } ).ToListAsync(); var enemyMap = new Features.Spatial.Quadtree(enemyVillages); var frontlineVillages = ownVillageData.Where(v => enemyMap.ContainsInRange(v.X, v.Y, 4.0f)).ToList(); var frontlineVillageIds = frontlineVillages.Select(v => v.VillageId).ToList(); var attacksOnFrontline = await( from command in CurrentSets.Command where frontlineVillageIds.Contains(command.TargetVillageId) where command.IsAttack where command.LandsAt > serverTime select new { command.SourceVillageId, command.TargetVillageId } ).ToListAsync(); var supportToFrontline = await( from support in CurrentSets.Command where frontlineVillageIds.Contains(support.TargetVillageId) where support.LandsAt > serverTime where support.ArmyId != null select new { support.TargetVillageId, support.Army } ).ToListAsync(); var attackingVillageIds = attacksOnFrontline.Select(c => c.SourceVillageId).Distinct().ToList(); var attackingVillages = await CurrentSets .CurrentVillage .Where(v => attackingVillageIds.Contains(v.VillageId)) .Select(v => new { v.VillageId, v.ArmyOwned, v.ArmyStationed, v.ArmyTraveling }) .ToDictionaryAsync( v => v.VillageId, v => v ); var attackingVillagesWithNukes = attackingVillages // Get possible troops from village .Select(kvp => new { kvp.Key, Army = JSON.Army.Max(kvp.Value.ArmyOwned, kvp.Value.ArmyStationed, kvp.Value.ArmyTraveling) }) // Get population of offensive troops .Select(kvp => new { kvp.Key, OffensivePop = ArmyStats.CalculateTotalPopulation(kvp.Army, ArmyStats.OffensiveTroopTypes.Except(new[] { JSON.TroopType.Heavy }).ToArray()) }) // Filter by full nukes .Where(kvp => kvp.OffensivePop > 0.65f * Features.CommandClassification.Utils.FullArmyPopulation) .Select(kvp => kvp.Key) .ToList(); var nukesSentPerVillage = frontlineVillageIds.ToDictionary(id => id, id => 0); foreach (var attack in attacksOnFrontline.Where(a => attackingVillagesWithNukes.Contains(a.SourceVillageId))) { nukesSentPerVillage[attack.TargetVillageId]++; } var pendingSupportPerVillage = frontlineVillageIds.ToDictionary(id => id, id => new JSON.Army()); foreach (var support in supportToFrontline) { pendingSupportPerVillage[support.TargetVillageId] += support.Army; } var battleSimulator = new Features.Simulation.BattleSimulator(); var nukesEatablePerVillage = frontlineVillages.ToDictionary( v => v.VillageId, v => battleSimulator.EstimateRequiredNukes(v.ArmyStationed + pendingSupportPerVillage[v.VillageId], 20, CurrentWorldSettings.ArchersEnabled, 100).NukesRequired ); return(frontlineVillages .Where(v => nukesSentPerVillage[v.VillageId] > 0 && nukesEatablePerVillage[v.VillageId] - nukesSentPerVillage[v.VillageId] < 2) .Select(v => new { v.VillageId, v.VillageName, v.X, v.Y, SentNukes = nukesSentPerVillage[v.VillageId], EatableNukes = nukesEatablePerVillage[v.VillageId] }) .OrderByDescending(v => v.SentNukes - v.EatableNukes) .ThenBy(v => v.VillageId) .ToList()); } async Task <object> GetNobleTargetSuggestions(CurrentContextDbSets CurrentSets) { var villasWithNobles = await( from village in CurrentSets.Village join currentVillage in CurrentSets.CurrentVillage on village.VillageId equals currentVillage.VillageId where currentVillage.ArmyOwned.Snob > 0 where village.PlayerId == CurrentPlayerId select new Coordinate { X = village.X.Value, Y = village.Y.Value } ).ToListAsync(); var enemyCurrentVillas = await( from currentVillage in CurrentSets.CurrentVillage join village in CurrentSets.Village on currentVillage.VillageId equals village.VillageId where !CurrentSets.ActiveUser.Any(au => au.PlayerId == village.PlayerId) select new { X = village.X.Value, Y = village.Y.Value, village.VillageId, village.Points, currentVillage.Loyalty, currentVillage.LoyaltyLastUpdated, currentVillage.ArmyStationed, village.PlayerId, VillageName = village.VillageName.UrlDecode(), PlayerName = village.Player.PlayerName.UrlDecode() } ).ToListAsync(); var villageMap = new Features.Spatial.Quadtree(villasWithNobles); var loyaltyCalculator = new Features.Simulation.LoyaltyCalculator(CurrentWorldSettings.GameSpeed); var possibleTargets = enemyCurrentVillas .Where(v => villageMap.ContainsInRange(v.X, v.Y, 7.5f)) // Only consider enemy villas within 7.5 fields of any villa with nobles .Select(v => { var possibleLoyalty = v.Loyalty.HasValue ? loyaltyCalculator.PossibleLoyalty(v.Loyalty.Value, serverTime - v.LoyaltyLastUpdated.Value) : 100; var stationedDVs = ArmyStats.CalculateTotalPopulation(v.ArmyStationed, ArmyStats.DefensiveTroopTypes) / (float)Features.CommandClassification.Utils.FullArmyPopulation; // Select "confidence" in selecting the given target as a suggestion // If < 0.75 DV stationed or loyalty under 50, 100% confident in the suggestion var loyaltyConfidence = 1.0f - (possibleLoyalty - 50) / 50.0f; var stackConfidence = 1.0f - (stationedDVs - 0.75f) / 0.75f; if (v.ArmyStationed?.LastUpdated != null) { var stackAge = serverTime - v.ArmyStationed.LastUpdated.Value; var ageFactor = (TimeSpan.FromHours(48) - stackAge) / TimeSpan.FromHours(48); stackConfidence *= Math.Max(0, (float)Math.Pow(Math.Abs(ageFactor), 0.5f) * Math.Sign(ageFactor)); } else { stackConfidence = 0; } return(new { Loyalty = possibleLoyalty, StationedDVs = stationedDVs, DVsSeenAt = v.ArmyStationed?.LastUpdated, Confidence = loyaltyConfidence + stackConfidence, Village = v }); }); var confidentTargets = possibleTargets.Where(t => t.Confidence > 1).ToList(); return(confidentTargets .Select(t => new { t.Village.X, t.Village.Y, t.Village.VillageId, t.Village.VillageName, t.Village.PlayerId, t.Village.PlayerName, t.Village.Points, t.Loyalty, t.StationedDVs, t.DVsSeenAt, t.Confidence }) .OrderByDescending(t => t.Confidence) .ThenBy(t => t.VillageId) .ToList()); } async Task <object> GetUselessStackSuggestions(CurrentContextDbSets CurrentSets) { var enemyVillages = await( from enemy in CurrentSets.EnemyTribe join player in CurrentSets.Player on enemy.EnemyTribeId equals player.TribeId join village in CurrentSets.Village on player.PlayerId equals village.PlayerId select new Coordinate { X = village.X.Value, Y = village.Y.Value } ).ToListAsync(); var ownSupport = await( from support in CurrentSets.CurrentVillageSupport join targetVillage in CurrentSets.Village on support.TargetVillageId equals targetVillage.VillageId join sourceVillage in CurrentSets.Village on support.SourceVillageId equals sourceVillage.VillageId where targetVillage.PlayerId == null || !vaultPlayerIds.Contains(targetVillage.PlayerId.Value) where sourceVillage.PlayerId == CurrentPlayerId select new { support.SupportingArmy, targetVillage.VillageId } ).ToListAsync(); var notOwnVillageIds = ownSupport.Select(s => s.VillageId).Distinct().ToList(); var notOwnVillages = await( from village in CurrentSets.Village where notOwnVillageIds.Contains(village.VillageId) select new { X = village.X.Value, Y = village.Y.Value, VillageName = village.VillageName.UrlDecode(), village.VillageId } ).ToListAsync(); var enemyMap = new Features.Spatial.Quadtree(enemyVillages); var backlineVillages = ownVillageData.Where(v => !enemyMap.ContainsInRange(new Coordinate { X = v.X, Y = v.Y }, 10)); var stackInfo = backlineVillages // Select backline villages with over 3k pop of support .Select(v => new { Village = new { v.X, v.Y, v.VillageName, v.VillageId }, Population = ArmyStats.CalculateTotalPopulation((JSON.Army)v.ArmyStationed - (JSON.Army)v.ArmyAtHome) }) .Where(v => v.Population > 3000) .Select(v => new { v.Village.X, v.Village.Y, v.Village.VillageName, v.Village.VillageId, PopCount = v.Population }) // Include support to unknown villages .Concat(notOwnVillages.Select(v => new { v.X, v.Y, v.VillageName, v.VillageId, PopCount = ownSupport.Where(s => s.VillageId == v.VillageId).Sum(s => ArmyStats.CalculateTotalPopulation(s.SupportingArmy)) })) .ToList(); var villageIds = stackInfo.Select(i => i.VillageId).Distinct().ToList(); var villageOwnersById = await( from village in CurrentSets.Village join player in CurrentSets.Player on village.PlayerId equals player.PlayerId let tribe = (from tribe in CurrentSets.Ally where tribe.TribeId == player.TribeId select new { tribe.Tag, tribe.TribeId }).FirstOrDefault() where villageIds.Contains(village.VillageId) select new { village.VillageId, Player = player, Tribe = tribe } ).ToDictionaryAsync( v => v.VillageId, v => new { v.Player.PlayerName, v.Player.PlayerId, TribeName = v.Tribe?.Tag, v.Tribe?.TribeId } ); return(stackInfo .Select(info => { var owner = villageOwnersById.GetValueOrDefault(info.VillageId); return new { info.VillageId, info.VillageName, info.PopCount, info.X, info.Y, PlayerName = owner?.PlayerName == null ? null : owner.PlayerName.UrlDecode(), owner?.PlayerId, TribeName = owner?.TribeName == null ? null : owner.TribeName.UrlDecode(), owner?.TribeId }; })); } (var recaps, var snipes, var stacks, var nobles, var uselessStacks) = await ManyTasks.Run( WithTemporarySets(GetRecapSuggestions), WithTemporarySets(GetSnipeSuggestions), WithTemporarySets(GetStackSuggestions), WithTemporarySets(GetNobleTargetSuggestions), WithTemporarySets(GetUselessStackSuggestions) ); return(Ok(new { Recaps = recaps, Snipes = snipes, Stacks = stacks, NobleTargets = nobles, UselessStacks = uselessStacks })); }