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> GetCommandsRegardingVillage(long villageId) { if (!await CanReadVillage(villageId)) return StatusCode(401); 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 commandsFromVillage = await Profile("Get commands from village", () => ( from command in CurrentSets.Command .FromWorld(CurrentWorldId) .Include(c => c.Army) where command.SourceVillageId == villageId where command.ReturnsAt > CurrentServerTime orderby command.ReturnsAt ascending select command ).ToListAsync()); foreach (var command in commandsFromVillage.Where(c => !c.IsReturning)) { if (command.LandsAt <= CurrentServerTime) command.IsReturning = true; } await context.SaveChangesAsync(); var targetVillageIds = commandsFromVillage.Select(c => c.TargetVillageId).Distinct(); var targetVillages = await Profile("Get other villages", () => ( from village in CurrentSets.Village where targetVillageIds.Contains(village.VillageId) select village ).ToListAsync()); var targetVillagesById = targetVillages.ToDictionary(v => v.VillageId, v => v); var result = new JSON.VillageCommandSet(); if (commandsFromVillage != null && commandsFromVillage.Count > 0) { result.CommandsFromVillage = new List<JSON.VillageCommand>(); foreach (var command in commandsFromVillage) { var commandData = new JSON.VillageCommand(); commandData.CommandId = command.CommandId; commandData.LandsAt = command.LandsAt; commandData.ReturnsAt = command.ReturnsAt.Value; commandData.Army = ArmyConvert.ArmyToJson(command.Army); commandData.IsReturning = command.IsReturning; commandData.TroopType = command.TroopType; commandData.OtherVillageId = command.TargetVillageId; var otherVillage = targetVillagesById[command.TargetVillageId]; commandData.OtherVillageName = otherVillage.VillageName.UrlDecode(); commandData.OtherVillageCoords = $"{otherVillage.X}|{otherVillage.Y}"; result.CommandsFromVillage.Add(commandData); } } return Ok(result); }
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> GetIncomingTags([FromBody] List <long> incomingsIds) { // Preload world data since we need world settings within queries below PreloadWorldData(); // Lots of data read but only updating some of it; whenever we do SaveChanges it checks // for changes against all queried objects. Disable tracking by default and track explicitly if necessary context.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking; var incomingData = await Profile("Get existing commands", () => ( from command in CurrentSets.Command .Include(c => c.SourceVillage) let currentVillage = command.SourceVillage.CurrentVillage.FirstOrDefault(cv => cv.AccessGroupId == CurrentAccessGroupId) where incomingsIds.Contains(command.CommandId) select new { Command = command, CurrentVillage = currentVillage } ).ToListAsync() ); if (incomingData == null) { return(NotFound()); } // Load in actual CurrentArmy data for incomings // (Didn't need this previously but EF Core can be dumb, .Include on a `join .. into` doesn't actually include the given properties) { IEnumerable <long> SelectCurrentArmyIds(Scaffold.CurrentVillage currentVillage) { if (currentVillage == null) { yield break; } if (currentVillage.ArmyOwnedId != null) { yield return(currentVillage.ArmyOwnedId.Value); } if (currentVillage.ArmyStationedId != null) { yield return(currentVillage.ArmyStationedId.Value); } if (currentVillage.ArmyTravelingId != null) { yield return(currentVillage.ArmyTravelingId.Value); } } var currentArmyIds = incomingData.SelectMany(d => SelectCurrentArmyIds(d.CurrentVillage)).ToList(); var currentArmies = await CurrentSets.CurrentArmy.Where(army => currentArmyIds.Contains(army.ArmyId)).ToDictionaryAsync(a => a.ArmyId, a => a); foreach (var village in incomingData.Select(d => d.CurrentVillage).Where(v => v != null)) { if (village.ArmyOwnedId != null) { village.ArmyOwned = currentArmies[village.ArmyOwnedId.Value]; } if (village.ArmyStationedId != null) { village.ArmyStationed = currentArmies[village.ArmyStationedId.Value]; } if (village.ArmyTravelingId != null) { village.ArmyTraveling = currentArmies[village.ArmyTravelingId.Value]; } } } var uploadHistory = await Profile("Get user upload history", () => context.UserUploadHistory.Where(h => h.Uid == CurrentUserId).FirstOrDefaultAsync() ); var validationInfo = UploadRestrictionsValidate.ValidateInfo.FromTaggingRestrictions(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" } // NOTE - We pull data for all villas requested but only return data for villas not in vaultOwnedVillages, // should stop querying that other data at some point var commandSourceVillageIds = incomingData.Select(inc => inc.Command.SourceVillageId).Distinct().ToList(); var commandTargetVillageIds = incomingData.Select(inc => inc.Command.TargetVillageId).Distinct().ToList(); var relevantVillages = await( from village in CurrentSets.Village where commandSourceVillageIds.Contains(village.VillageId) || commandTargetVillageIds.Contains(village.VillageId) select new { village.PlayerId, village.VillageId, village.VillageName, X = village.X.Value, Y = village.Y.Value } ).ToDictionaryAsync(v => v.VillageId, v => v); var sourcePlayerIds = relevantVillages.Values.Where(v => commandSourceVillageIds.Contains(v.VillageId)).Select(v => v.PlayerId ?? 0).ToList(); // Don't do any tagging for villages owned by players registered with the vault (so players in other tribes // also using the vault can't infer villa builds) var vaultOwnedVillages = await Profile("Get villages owned by vault users", () => ( from user in CurrentSets.User join village in CurrentSets.Village on user.PlayerId equals village.PlayerId where user.Enabled select village.VillageId ).ToListAsync()); var sourcePlayerNames = await Profile("Get player names", () => ( from player in CurrentSets.Player where sourcePlayerIds.Contains(player.PlayerId) select new { player.PlayerId, player.PlayerName } ).ToDictionaryAsync(p => p.PlayerId, p => p.PlayerName)); var countsByVillage = await Profile("Get command counts", () => ( from command in CurrentSets.Command where !command.IsReturning && command.LandsAt > CurrentServerTime group command by command.SourceVillageId into villageCommands select new { VillageId = villageCommands.Key, Count = villageCommands.Count() } ).ToDictionaryAsync(vc => vc.VillageId, vc => vc.Count)); var travelCalculator = new Features.Simulation.TravelCalculator(CurrentWorldSettings.GameSpeed, CurrentWorldSettings.UnitSpeed); DateTime CommandLaunchedAt(Scaffold.Command command) => command.LandsAt - travelCalculator.CalculateTravelTime( (command.TroopType ?? "ram").ToTroopType(), relevantVillages[command.SourceVillageId].X, relevantVillages[command.SourceVillageId].Y, relevantVillages[command.TargetVillageId].X, relevantVillages[command.TargetVillageId].Y ); var earliestLaunchTime = incomingData.Select(inc => CommandLaunchedAt(inc.Command)).DefaultIfEmpty(CurrentServerTime).Min(); var commandsReturningByVillageId = await Profile("Process returning commands for all source villages", async() => { var commandSeenThreshold = earliestLaunchTime - TimeSpan.FromDays(1); var sentCommands = await Profile("Query returning commands for all source villages", () => ( from command in CurrentSets.Command.AsTracking() .Include(c => c.Army) where command.FirstSeenAt > commandSeenThreshold where command.Army != null where commandSourceVillageIds.Contains(command.SourceVillageId) select command ).ToListAsync()); bool updatedCommands = false; var result = commandSourceVillageIds.ToDictionary(vid => vid, vid => new List <Scaffold.Command>()); Profile("Update command returning and sort into dictionary", () => { foreach (var cmd in sentCommands) { if (cmd.LandsAt <= CurrentServerTime) { if (!cmd.IsReturning) { updatedCommands = true; cmd.IsReturning = true; } result[cmd.SourceVillageId].Add(cmd); } } }); if (updatedCommands) { await Profile("Save commands now set to returning", () => context.SaveChangesAsync()); } return(result); }); var otherTargetedVillageIds = commandsReturningByVillageId.SelectMany(kvp => kvp.Value).Select(c => c.TargetVillageId).Distinct().Except(relevantVillages.Keys); var otherTargetVillages = await Profile("Get other villages targeted by inc source villas", () => CurrentSets.Village.Where(v => otherTargetedVillageIds.Contains(v.VillageId)).ToListAsync() ); foreach (var id in otherTargetedVillageIds) { var village = otherTargetVillages.First(v => v.VillageId == id); relevantVillages.Add(id, new { village.PlayerId, village.VillageId, village.VillageName, X = village.X.Value, Y = village.Y.Value }); } var launchTimesByCommandId = commandsReturningByVillageId.SelectMany(kvp => kvp.Value).Where(cmd => !vaultOwnedVillages.Contains(cmd.SourceVillageId)).ToDictionary( cmd => cmd.CommandId, cmd => CommandLaunchedAt(cmd) ); IEnumerable <Scaffold.Command> RelevantCommandsForIncoming(Scaffold.Command incoming) { if (!relevantVillages.ContainsKey(incoming.SourceVillageId)) { return(Enumerable.Empty <Scaffold.Command>()); } var launchTime = CommandLaunchedAt(incoming); var returningCommands = commandsReturningByVillageId.GetValueOrDefault(incoming.SourceVillageId); if (returningCommands == null) { return(Enumerable.Empty <Scaffold.Command>()); } return(returningCommands.Where(cmd => cmd.ReturnsAt > launchTime || (launchTimesByCommandId.ContainsKey(cmd.CommandId) && launchTimesByCommandId[cmd.CommandId] > launchTime))); } var duplicates = incomingData.GroupBy(i => i.Command.CommandId).Where(g => g.Count() > 1).ToDictionary(g => g.Key, g => g.ToList()); Dictionary <long, JSON.IncomingTag> resultTags = new Dictionary <long, JSON.IncomingTag>(); Profile("Make incomings tags", () => { foreach (var data in incomingData) { var incoming = data.Command; var sourceVillageId = incoming.SourceVillageId; if (vaultOwnedVillages.Contains(sourceVillageId)) { continue; } var sourceCurrentVillage = data.CurrentVillage; var commandsReturning = RelevantCommandsForIncoming(incoming); var armyOwned = sourceCurrentVillage?.ArmyOwned; var armyTraveling = sourceCurrentVillage?.ArmyTraveling; var armyStationed = sourceCurrentVillage?.ArmyStationed; // TODO - Make this a setting var maxUpdateTime = TimeSpan.FromDays(4); if (armyOwned?.LastUpdated != null && (CurrentServerTime - armyOwned.LastUpdated.Value > maxUpdateTime)) { armyOwned = null; } if (armyTraveling?.LastUpdated != null && (CurrentServerTime - armyTraveling.LastUpdated.Value > maxUpdateTime)) { armyTraveling = null; } if (armyStationed?.LastUpdated != null && (CurrentServerTime - armyStationed.LastUpdated.Value > maxUpdateTime)) { armyStationed = null; } if (armyOwned != null && armyOwned.IsEmpty()) { armyOwned = null; } if (armyTraveling != null && armyTraveling.IsEmpty()) { armyTraveling = null; } if (armyStationed != null && armyStationed.IsEmpty()) { armyStationed = null; } var troopsReturning = new JSON.Army(); if (commandsReturning != null) { foreach (var command in commandsReturning) { troopsReturning += ArmyConvert.ArmyToJson(command.Army); } } Scaffold.CurrentArmy effectiveArmy = null; bool isConfidentArmy = true; if (armyOwned != null) { effectiveArmy = armyOwned; } else if (armyTraveling != null) { effectiveArmy = armyTraveling; } else if (armyStationed != null) { effectiveArmy = armyStationed; isConfidentArmy = false; } var tag = new JSON.IncomingTag(); tag.CommandId = incoming.CommandId; tag.OriginalTag = incoming.UserLabel; tag.NumFromVillage = countsByVillage.GetValueOrDefault(sourceVillageId); tag.TroopType = TroopTypeConvert.StringToTroopType(incoming.TroopType); var sourceVillage = relevantVillages[incoming.SourceVillageId]; var targetVillage = relevantVillages[incoming.TargetVillageId]; tag.SourceVillageCoords = $"{sourceVillage.X}|{sourceVillage.Y}"; tag.TargetVillageCoords = $"{targetVillage.X}|{targetVillage.Y}"; tag.SourcePlayerName = sourcePlayerNames.GetValueOrDefault(incoming.SourcePlayerId, Translate("UNKNOWN")).UrlDecode(); tag.SourceVillageName = sourceVillage.VillageName.UrlDecode(); tag.TargetVillageName = targetVillage.VillageName.UrlDecode(); tag.Distance = new Coordinate { X = sourceVillage.X, Y = sourceVillage.Y }.DistanceTo(targetVillage.X, targetVillage.Y); if (effectiveArmy != null) { // TODO - Make this a setting bool isOffense = ArmyStats.IsOffensive(effectiveArmy); tag.VillageType = isOffense ? Translate("OFFENSE") : Translate("DEFENSE"); if (!isOffense && isConfidentArmy && (effectiveArmy.Snob == null || effectiveArmy.Snob == 0) && incoming.TroopType != JSON.TroopType.Snob.ToTroopString()) { tag.DefiniteFake = true; } var offensiveArmy = effectiveArmy.OfType(JSON.UnitBuild.Offensive); var jsonArmy = ArmyConvert.ArmyToJson(offensiveArmy); var pop = Native.ArmyStats.CalculateTotalPopulation(jsonArmy); var returningOffensiveArmy = troopsReturning.OfType(JSON.UnitBuild.Offensive); var returningPop = Native.ArmyStats.CalculateTotalPopulation(returningOffensiveArmy); tag.OffensivePopulation = pop - returningPop; if (tag.OffensivePopulation < 0) { tag.OffensivePopulation = 0; } if ((tag.OffensivePopulation > 100 || returningPop > 5000) && tag.OffensivePopulation < 5000 && isConfidentArmy) { tag.DefiniteFake = true; } tag.ReturningPopulation = returningPop; tag.NumCats = effectiveArmy.Catapult; } resultTags.Add(incoming.CommandId, tag); } }); return(Ok(resultTags)); }