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 JSON.TroopType TravelTroopType(JSON.Army army) { JSON.TroopType slowestType = JSON.TroopType.Spy; foreach (var type in army.Where(kvp => kvp.Value > 0).Select(kvp => kvp.Key)) { if (Native.ArmyStats.TravelSpeed[type] > Native.ArmyStats.TravelSpeed[slowestType]) { slowestType = type; } } return(slowestType); }
public static JSON.Army OfType(this JSON.Army army, JSON.UnitBuild build) { if (army == null) { return(new JSON.Army()); } var result = new JSON.Army(); foreach (var type in army.Keys) { if (Native.ArmyStats.UnitBuild[type] == build) { result.Add(type, army[type]); } } return(result); }
public static Army Max(params Army[] armies) { var result = new Army(); foreach (var army in armies.Where(a => a != null)) { foreach (var type in army.Keys) { if (!result.ContainsKey(type)) { result.Add(type, army[type]); } else if (result[type] < army[type]) { result[type] = army[type]; } } } return(result); }
public static bool IsStacked(JSON.Army army) { return(BattleSimulator.TotalDefensePower(army) >= StackDefensePower); }
public static bool IsOffensive(JSON.Army army) { return(army.GetValueOrDefault(JSON.TroopType.Axe, 0) > 500 || army.GetValueOrDefault(JSON.TroopType.Light, 0) > 250); }
public JSON.Army CalculatePossibleArmyRecruitment(TimeSpan timeSpan, List <JSON.TroopType> troopTypes, Dictionary <JSON.TroopType, int> maxCounts = null) { // Keep time span reasonable so we don't get integer overflow if (timeSpan.TotalDays > 30) { timeSpan = TimeSpan.FromDays(30); } var result = new JSON.Army(); foreach (var type in troopTypes) { result.Add(type, 0); } var typesBySource = troopTypes.GroupBy(t => Native.ArmyStats.UnitSource[t]).ToDictionary((s) => s.Key, s => s.Select(t => t)); // Simulate recruitment by time interval var interval = TimeSpan.FromHours(6); var simulatedTime = TimeSpan.Zero; int totalPop = 0; int previousPop = -1; bool ReachedCountLimit(JSON.TroopType troopType) { return(maxCounts != null && maxCounts.ContainsKey(troopType) && result[troopType] >= maxCounts[troopType]); } while (simulatedTime < timeSpan && totalPop < MaxPopulation && totalPop != previousPop) { foreach (var source in typesBySource.Keys) { var types = typesBySource[source].Where(t => !ReachedCountLimit(t)); var sumTimeSeconds = types.Sum(t => Native.ArmyStats.BaseRecruitTime[t].TotalSeconds); foreach (var type in types) { var factor = 1 - Native.ArmyStats.BaseRecruitTime[type].TotalSeconds / sumTimeSeconds; // If there's only 1 unit type for this building if (factor <= float.Epsilon) { factor = 1; } var troops = CalculatePossibleUnitRecruitment(type, interval * factor); result[type] += troops; } } if (maxCounts != null) { foreach (var type in maxCounts.Keys) { if (!result.ContainsKey(type)) { continue; } if (result[type] > maxCounts[type]) { result[type] = maxCounts[type]; } } } // Keep track of previous/total in case we reach max counts before time limit or pop limit are reached // (otherwise we may get infinite loop) previousPop = totalPop; totalPop = Native.ArmyStats.CalculateTotalPopulation(result); simulatedTime += interval; } return(result); }
public static JSON.Army ArmyToJson(Scaffold.IScaffoldArmy army) { if (army == null) { return(null); } var result = new JSON.Army(); if (army.Spear != null) { result[JSON.TroopType.Spear] = army.Spear.Value; } if (army.Sword != null) { result[JSON.TroopType.Sword] = army.Sword.Value; } if (army.Axe != null) { result[JSON.TroopType.Axe] = army.Axe.Value; } if (army.Archer != null) { result[JSON.TroopType.Archer] = army.Archer.Value; } if (army.Spy != null) { result[JSON.TroopType.Spy] = army.Spy.Value; } if (army.Light != null) { result[JSON.TroopType.Light] = army.Light.Value; } if (army.Marcher != null) { result[JSON.TroopType.Marcher] = army.Marcher.Value; } if (army.Heavy != null) { result[JSON.TroopType.Heavy] = army.Heavy.Value; } if (army.Ram != null) { result[JSON.TroopType.Ram] = army.Ram.Value; } if (army.Catapult != null) { result[JSON.TroopType.Catapult] = army.Catapult.Value; } if (army.Knight != null) { result[JSON.TroopType.Knight] = army.Knight.Value; } if (army.Snob != null) { result[JSON.TroopType.Snob] = army.Snob.Value; } if (army.Militia != null) { result[JSON.TroopType.Militia] = army.Militia.Value; } return(result); }
private static T JsonToArmy <T>(JSON.Army armyCounts, short worldId, T existingArmy = null, Scaffold.VaultContext context = null, bool emptyIfNull = false) where T : class, new() { if (Object.ReferenceEquals(armyCounts, null)) { if (emptyIfNull) { armyCounts = JSON.Army.Empty; } else { if (existingArmy != null && context != null) { context.Remove(existingArmy); } return(null); } } T result; if (existingArmy != null) { result = existingArmy; } else { result = new T(); } var scaffoldArmyType = typeof(T); foreach (var troopType in Enum.GetValues(typeof(JSON.TroopType)).Cast <JSON.TroopType>()) { var troopCount = GetOrNull(armyCounts, troopType); var troopProperty = scaffoldArmyType.GetProperty(troopType.ToString(), System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance); var currentCount = (int?)troopProperty.GetValue(result); if (currentCount == troopCount) { continue; } if (typeof(short?).IsAssignableFrom(troopProperty.PropertyType)) { troopProperty.SetValue(result, troopCount?.ToShort()); } else { troopProperty.SetValue(result, troopCount); } } var worldIdProperty = scaffoldArmyType.GetProperty("WorldId", System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance); if (worldIdProperty != null) { worldIdProperty.SetValue(result, worldId); } if (existingArmy == null && context != null) { context.Add(result); } return(result); }
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 TimeSpan ArmyFieldSpeed(JSON.Army army) { var slowestSpeed = Native.ArmyStats.TravelSpeed[TravelTroopType(army)]; return(TimeSpan.FromMinutes(slowestSpeed)); }
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)); }