public static Scaffold.CurrentArmy OfType(this Scaffold.CurrentArmy army, JSON.UnitBuild build) { var result = new Scaffold.CurrentArmy(); switch (build) { case JSON.UnitBuild.Offensive: result.Axe = army.Axe; result.Light = army.Light; result.Marcher = army.Marcher; result.Ram = army.Ram; result.Catapult = army.Catapult; result.Snob = army.Snob; break; case JSON.UnitBuild.Defensive: result.Spear = army.Spear; result.Sword = army.Sword; result.Archer = army.Archer; result.Heavy = army.Heavy; result.Militia = army.Militia; break; case JSON.UnitBuild.Neutral: result.Spy = army.Spy; result.Knight = army.Knight; break; } return(result); }
public static bool IsEmpty(this Scaffold.CurrentArmy army) { return(army == null || army.LastUpdated == null || ( (army.Spear == null || army.Spear.Value == 0) && (army.Sword == null || army.Sword.Value == 0) && (army.Axe == null || army.Axe.Value == 0) && (army.Archer == null || army.Archer.Value == 0) && (army.Spy == null || army.Spy.Value == 0) && (army.Light == null || army.Light.Value == 0) && (army.Marcher == null || army.Marcher.Value == 0) && (army.Heavy == null || army.Heavy.Value == 0) && (army.Ram == null || army.Ram.Value == 0) && (army.Catapult == null || army.Catapult.Value == 0) && (army.Knight == null || army.Knight.Value == 0) && (army.Snob == null || army.Snob.Value == 0) && (army.Militia == null || army.Militia.Value == 0) )); }
public static Scaffold.CurrentArmy JsonToArmy(JSON.Army armyCounts, short worldId, Scaffold.CurrentArmy existingArmy = null, Scaffold.VaultContext context = null, bool emptyIfNull = false) => JsonToArmy <Scaffold.CurrentArmy>(armyCounts, worldId, existingArmy, context, emptyIfNull);
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)); }