Example #1
0
 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));
        }
Example #3
0
    public KingdomTest(float populationGrowth, RACE race, List <CityTileTest> cities, Color tileColor, KingdomTileTest kingdomTile)
    {
        this.id          = GetID() + 1;
        this.kingdomName = "KINGDOM" + this.id;
        this.kingdomTile = kingdomTile;
        this.lord        = null;
//		this.lord = new Lord(this);
//		this.assignedLord = new Royalty (this, true);
        this.cities                   = cities;
        this.kingdomRace              = race;
        this.isDead                   = false;
        this.tileColor                = tileColor;
        this.relationshipKingdoms     = new List <RelationshipKingdoms>();
        this.expansionChance          = defaultExpansionChance;
        this.armyBaseUnits            = GetArmyBaseUnits();
        this.armyIncreaseUnits        = GetArmyIncreaseUnits();
        this.armyBaseStats            = GetArmyBaseStats();
        this.armyIncreaseStats        = GetArmyIncreaseStats();
        this.armyIncreaseUnitResource = GetArmyIncreaseUnitResource();
        this.royaltyList              = new RoyaltyList();
        this.marriedCouples           = new List <MarriedCouple>();

        SetLastID(this.id);
        DetermineCityUpgradeResourceType();
        CreateInitialRoyalties();
    }
Example #4
0
    public Army(CityTest city)
    {
//		this.id = GetID () + 1;
//		this.name = "GENERAL" + this.id;
        this.armyCount      = city.kingdomTile.kingdom.armyBaseUnits;
        this.armyLevel      = 1;
        this.armyExperience = 0;
        this.armyStats      = new ArmyStats(city.kingdomTile.kingdom.armyBaseStats.hp, city.kingdomTile.kingdom.armyBaseStats.attack);
    }
Example #5
0
    /* De 2 collision gameobjects kunnen hier niet, want dan als een 3e partij collide dan worden ze geupdate hier en klopt het gevecht niet meer.
     *
     */

    void OnTriggerEnter(Collider collision)

    // When an other prefab army is hit they collide, based on wether it is an enemy or ally it responds accordingly.
    {
        if (collision.gameObject.CompareTag("Town"))
        {
            ArmyStats myArmyStats = transform.GetComponent <ArmyStats>();
            CityStats cityStats   = collision.GetComponent <CityStats>();

            if (myArmyStats.playerNumber == cityStats.playerNumber && !myArmyStats.justSpawned) // Own city collision
            {
                cityStats.armySize += myArmyStats.armySize;
                cityStats.Update_Citystats();
                Destroy(myArmyStats.gameObject);
            }
            else // TODO enemy city collision
            {
            }
        }
        else if (collision.gameObject.CompareTag("Army")) // Collision between armies
        {
            ArmyStats myArmyStats    = transform.GetComponent <ArmyStats>();
            ArmyStats enemyArmyStats = collision.GetComponent <ArmyStats>();

            if (enemyArmyStats.playerNumber == myArmyStats.playerNumber) // Own Team collision
            {
                // spawnNumber < spawnNumber is so that the collision only gets done once, because this method is being invoked on both objects
                if (enemyArmyStats.spawnNumber < myArmyStats.spawnNumber && !myArmyStats.inBattle && !enemyArmyStats.inBattle)
                {
                    OwnTeamCollision(collision);
                }

                else if (!enemyArmyStats.inBattle && myArmyStats.inBattle) // If one of the 2 is in battle
                {
                    myArmyStats.armySize += enemyArmyStats.armySize;
                    Destroy(enemyArmyStats.gameObject);
                }
            }
            else // Enemy Team collision
            {
                if (enemyArmyStats.spawnNumber < myArmyStats.spawnNumber && !myArmyStats.inBattle && !enemyArmyStats.inBattle)
                {
                    EnemyTeamCollision(collision);
                }

                else if (!enemyArmyStats.inBattle && myArmyStats.inBattle)
                {
                    myArmyStats.inBattleWith.GetComponent <ArmyStats>().armySize += enemyArmyStats.armySize;
                    Destroy(enemyArmyStats.gameObject);
                }
            }
        }
    }
Example #6
0
    IEnumerator InstantiateArmy(CityStats buttonParent) // Instantiates an army
    {
        bool    playerClicked       = false;            // Only instantiate when a player clickes to move it immediately
        Vector3 playerClickedTarget = Vector3.zero;
        float   armysize            = buttonParent.armySize;

        buttonParent.armySize = 0; // The city where the army spawned from now has 0 armies
        buttonParent.Update_Citystats();

        while (!playerClicked) // Waits untill the player clicks before instantiating unit
        {
            RaycastHit hit;
            Ray        ray = Camera.main.ScreenPointToRay(Input.mousePosition);

            if (Physics.Raycast(ray, out hit))
            {
                if (Input.GetMouseButtonDown(1))
                {
                    playerClicked       = true;
                    playerClickedTarget = new Vector3(hit.point.x, 0f, hit.point.z);
                }
            }
            yield return(null);
        }
        // Make a new army
        GameObject newArmy = Instantiate(army, buttonParent.transform.position, Quaternion.identity);

        ArmyStats newArmyStats = newArmy.GetComponent <ArmyStats>();

        newArmyStats.justSpawned  = true;                                  // So that it doesnt collide with the town immediately, used in Collide armies to not collide with a town
        newArmy.transform.parent  = GameObject.Find("_Dynamic").transform; // Becomes a dynamic object
        newArmyStats.armySize     = armysize;
        newArmyStats.isSelected   = true;                                  // The army spawned is selected
        newArmyStats.spawnNumber  = StaticLibrary.spawnArmyCounter;
        newArmyStats.playerNumber = buttonParent.playerNumber;

        newArmy.GetComponent <MoveArmies>().target = playerClickedTarget;
        newArmy.GetComponent <MoveArmies>().StartRoutine();

        StaticLibrary.DeselectTown();
        StaticLibrary.spawnArmyCounter++;

        playerClicked = false;
        yield return(new WaitForSeconds(0.5f)); // So that it doesnt collide with the town immediately

        newArmyStats.justSpawned = false;
    }
        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 async Task <Dictionary <String, UserStats> > GenerateHighScores(Scaffold.VaultContext context, int worldId, int accessGroupId, CancellationToken ct)
        {
            context.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;

            logger.LogDebug("Generating high scores for world {0}", worldId);

            var CurrentSets = new
            {
                ActiveUser            = context.User.Active().FromWorld(worldId).FromAccessGroup(accessGroupId),
                Player                = context.Player.FromWorld(worldId),
                Village               = context.Village.FromWorld(worldId),
                CurrentVillage        = context.CurrentVillage.FromWorld(worldId).FromAccessGroup(accessGroupId),
                Ally                  = context.Ally.FromWorld(worldId),
                CurrentVillageSupport = context.CurrentVillageSupport.FromWorld(worldId).FromAccessGroup(accessGroupId),
                Command               = context.Command.FromWorld(worldId).FromAccessGroup(accessGroupId),
                Report                = context.Report.FromWorld(worldId).FromAccessGroup(accessGroupId),
                EnemyTribe            = context.EnemyTribe.FromWorld(worldId).FromAccessGroup(accessGroupId)
            };

            var serverSettings = await context.WorldSettings.Where(s => s.WorldId == worldId).FirstOrDefaultAsync();

            var lastWeek = serverSettings.ServerTime - TimeSpan.FromDays(7);

            logger.LogDebug("Running data queries...");

            var tribePlayers = await(
                from user in CurrentSets.ActiveUser
                join player in CurrentSets.Player on user.PlayerId equals player.PlayerId
                select new { player.PlayerName, player.PlayerId }
                ).ToListAsync(ct);

            var tribeVillas = await(
                from user in CurrentSets.ActiveUser
                join player in CurrentSets.Player on user.PlayerId equals player.PlayerId
                join village in CurrentSets.Village on player.PlayerId equals village.PlayerId
                join currentVillage in CurrentSets.CurrentVillage
                on village.VillageId equals currentVillage.VillageId
                where currentVillage.ArmyAtHomeId != null && currentVillage.ArmyTravelingId != null
                select new { X = village.X.Value, Y = village.Y.Value, player.PlayerId, village.VillageId, currentVillage.ArmyAtHome, currentVillage.ArmyTraveling }
                ).ToListAsync(ct);

            var tribeSupport = await(
                from user in CurrentSets.ActiveUser
                join player in CurrentSets.Player on user.PlayerId equals player.PlayerId
                join village in CurrentSets.Village on player.PlayerId equals village.PlayerId
                join support in CurrentSets.CurrentVillageSupport
                on village.VillageId equals support.SourceVillageId
                select new { player.PlayerId, support.TargetVillageId, support.SupportingArmy }
                ).ToListAsync(ct);

            var tribeAttackCommands = await(
                from user in CurrentSets.ActiveUser
                join player in CurrentSets.Player on user.PlayerId equals player.PlayerId
                join command in CurrentSets.Command on player.PlayerId equals command.SourcePlayerId
                where command.ArmyId != null
                where command.LandsAt > lastWeek
                where command.IsAttack
                where command.TargetPlayerId != null
                select new { command.CommandId, command.SourcePlayerId, command.LandsAt, command.TargetVillageId, command.Army }
                ).ToListAsync(ct);

            var tribeSupportCommands = await(
                from user in CurrentSets.ActiveUser
                join player in CurrentSets.Player on user.PlayerId equals player.PlayerId
                join command in CurrentSets.Command on player.PlayerId equals command.SourcePlayerId
                where command.ArmyId != null
                where command.LandsAt > lastWeek
                where !command.IsAttack
                where command.TargetPlayerId != null
                select new SlimSupportCommand {
                SourcePlayerId = command.SourcePlayerId, TargetPlayerId = command.TargetPlayerId.Value, TargetVillageId = command.TargetVillageId, LandsAt = command.LandsAt
            }
                ).ToListAsync(ct);

            var tribeAttackingReports = await(
                from user in CurrentSets.ActiveUser
                join player in CurrentSets.Player on user.PlayerId equals player.PlayerId
                join report in CurrentSets.Report on player.PlayerId equals report.AttackerPlayerId
                where report.OccuredAt > lastWeek
                where report.AttackerArmy != null
                where report.DefenderPlayerId != null
                select new SlimReport {
                AttackerArmy = report.AttackerArmy, ReportId = report.ReportId, OccuredAt = report.OccuredAt, AttackerPlayerId = report.AttackerPlayerId.Value, DefenderVillageId = report.DefenderVillageId
            }
                ).ToListAsync(ct);

            var tribeDefendingReports = await(
                from user in CurrentSets.ActiveUser
                join player in CurrentSets.Player on user.PlayerId equals player.PlayerId
                join report in CurrentSets.Report on player.PlayerId equals report.DefenderPlayerId
                where report.OccuredAt > lastWeek
                where report.AttackerArmy != null
                select new SlimReport {
                AttackerArmy = report.AttackerArmy, DefenderVillageId = report.DefenderVillageId, ReportId = report.ReportId, OccuredAt = report.OccuredAt
            }
                ).ToListAsync(ct);

            var enemyVillas = 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 { X = village.X.Value, Y = village.Y.Value }
                ).ToListAsync(ct);

            if (ct.IsCancellationRequested)
            {
                return(null);
            }

            logger.LogDebug("Finished data queries");

            var tribeVillageIds = tribeVillas.Select(v => v.VillageId).ToList();

            var supportedVillageIds = tribeSupport.Select(s => s.TargetVillageId).Distinct().ToList();
            var villageTribeIds     = await(
                from village in CurrentSets.Village
                join player in CurrentSets.Player on village.PlayerId equals player.PlayerId
                join tribe in CurrentSets.Ally on player.TribeId equals tribe.TribeId
                where supportedVillageIds.Contains(village.VillageId)
                select new { village.VillageId, tribe.TribeId }
                ).ToDictionaryAsync(d => d.VillageId, d => d.TribeId, ct);

            if (ct.IsCancellationRequested)
            {
                return(null);
            }

            var supportedTribeIds = villageTribeIds.Values.Distinct().ToList();
            var tribeInfo         = await(
                from tribe in CurrentSets.Ally
                where supportedTribeIds.Contains(tribe.TribeId)
                select new { tribe.TribeId, tribe.TribeName, tribe.Tag }
                ).ToDictionaryAsync(d => d.TribeId, d => new { Name = d.TribeName, d.Tag }, ct);

            if (ct.IsCancellationRequested)
            {
                return(null);
            }

            logger.LogDebug("Finished supplemental queries");

            var defenseReportsWithNobles           = tribeDefendingReports.Where(r => r.AttackerArmy.Snob > 0).OrderBy(r => r.OccuredAt).ToList();
            var defenseNobleReportsByTargetVillage = defenseReportsWithNobles.GroupBy(r => r.DefenderVillageId).ToDictionary(g => g.Key, g => g.ToList());
            var possibleSnipesByTargetVillage      = tribeSupportCommands
                                                     .Where(c => defenseNobleReportsByTargetVillage.Keys.Contains(c.TargetVillageId))
                                                     .GroupBy(c => c.TargetVillageId, c => c)
                                                     .ToDictionary(g => g.Key, g => g.ToList());

            var numSnipesByPlayer = tribePlayers.ToDictionary(p => p.PlayerId, p => 0);

            foreach ((var villageId, var possibleSnipes) in possibleSnipesByTargetVillage.Tupled())
            {
                var attacksToVillage = defenseNobleReportsByTargetVillage[villageId];

                foreach (var snipe in possibleSnipes)
                {
                    var earlierReport = attacksToVillage.LastOrDefault(r => r.OccuredAt <= snipe.LandsAt);
                    var laterReport   = attacksToVillage.FirstOrDefault(r => r.OccuredAt > snipe.LandsAt);

                    if (laterReport == null)
                    {
                        continue;
                    }

                    if (earlierReport != null)
                    {
                        //  Check if between two nobles that landed at around the same time
                        if (laterReport.OccuredAt - earlierReport.OccuredAt < TimeSpan.FromMilliseconds(500))
                        {
                            numSnipesByPlayer[snipe.SourcePlayerId]++;
                        }
                    }
                    else if (laterReport.OccuredAt - snipe.LandsAt < TimeSpan.FromMilliseconds(1000))
                    {
                        // Landed before
                        numSnipesByPlayer[snipe.SourcePlayerId]++;
                    }
                }
            }



            var supportByTargetTribe = tribePlayers.ToDictionary(p => p.PlayerId, p => supportedTribeIds.ToDictionary(t => t, t => new List <Scaffold.CurrentArmy>()));

            foreach (var support in tribeSupport.Where(s => villageTribeIds.ContainsKey(s.TargetVillageId) && !tribeVillageIds.Contains(s.TargetVillageId)))
            {
                supportByTargetTribe[support.PlayerId][villageTribeIds[support.TargetVillageId]].Add(support.SupportingArmy);
            }

            logger.LogDebug("Sorted support by tribe");


            var reportsBySourcePlayer = tribePlayers.ToDictionary(p => p.PlayerId, _ => new Dictionary <long, List <SlimReport> >());

            foreach (var report in tribeAttackingReports)
            {
                var playerReports = reportsBySourcePlayer[report.AttackerPlayerId];
                if (!playerReports.ContainsKey(report.OccuredAt.Ticks))
                {
                    playerReports.Add(report.OccuredAt.Ticks, new List <SlimReport>());
                }
                playerReports[report.OccuredAt.Ticks].Add(report);
            }

            logger.LogDebug("Sorted reports by source player");

            var commandArmiesWithReports = new Dictionary <Scaffold.CommandArmy, SlimReport>();

            foreach (var command in tribeAttackCommands.Where(cmd => reportsBySourcePlayer.ContainsKey(cmd.SourcePlayerId)))
            {
                var matchingReport = reportsBySourcePlayer[command.SourcePlayerId].GetValueOrDefault(command.LandsAt.Ticks)?.FirstOrDefault(c => c.DefenderVillageId == command.TargetVillageId);
                if (matchingReport != null)
                {
                    commandArmiesWithReports.Add(command.Army, matchingReport);
                }
            }

            logger.LogDebug("Gathered commands with associated reports");

            var reportsWithoutCommands = tribeAttackingReports.Except(commandArmiesWithReports.Values).ToList();

            var usedAttackArmies = tribeAttackCommands
                                   .Select(c => new { c.SourcePlayerId, Army = (Army)c.Army })
                                   .Concat(reportsWithoutCommands.Select(r => new { SourcePlayerId = r.AttackerPlayerId, Army = (Army)r.AttackerArmy }))
                                   .ToList();

            logger.LogDebug("Gathered used attack armies");

            var usedAttackArmiesByPlayer = tribePlayers.ToDictionary(p => p.PlayerId, p => new List <Army>());

            foreach (var army in usedAttackArmies)
            {
                usedAttackArmiesByPlayer[army.SourcePlayerId].Add(army.Army);
            }

            logger.LogDebug("Sorted attack armies by player");

            var villagesByPlayer = tribeVillas.GroupBy(v => v.PlayerId).ToDictionary(g => g.Key, g => g.ToList());

            var armiesNearEnemy = new HashSet <long>();
            var enemyMap        = new Spatial.Quadtree(enemyVillas.Select(v => new Coordinate {
                X = v.X, Y = v.Y
            }));

            foreach (var village in tribeVillas.Where(v => enemyMap.ContainsInRange(new Coordinate {
                X = v.X, Y = v.Y
            }, 10)))
            {
                armiesNearEnemy.Add(village.ArmyAtHome.ArmyId);
            }

            logger.LogDebug("Collected armies near enemies");

            var result = new Dictionary <String, UserStats>();

            foreach (var player in tribePlayers)
            {
                if (ct.IsCancellationRequested)
                {
                    break;
                }

                var playerVillages = villagesByPlayer.GetValueOrDefault(player.PlayerId);
                var playerArmies   = usedAttackArmiesByPlayer[player.PlayerId];

                int numFangs = 0, numNukes = 0, numFakes = 0;
                foreach (var army in playerArmies)
                {
                    if (ArmyStats.IsFake(army))
                    {
                        numFakes++;
                    }
                    if (ArmyStats.IsNuke(army))
                    {
                        numNukes++;
                    }
                    if (ArmyStats.IsFang(army))
                    {
                        numFangs++;
                    }
                }

                var playerResult = new UserStats
                {
                    FangsInPastWeek   = numFangs,
                    NukesInPastWeek   = numNukes,
                    FakesInPastWeek   = numFakes,
                    SnipesInPastWeek  = numSnipesByPlayer[player.PlayerId],
                    BacklineDVsAtHome = playerVillages?.Where(v => !armiesNearEnemy.Contains(v.ArmyAtHome.ArmyId)).Sum(v => BattleSimulator.TotalDefensePower(v.ArmyAtHome) / (float)ArmyStats.FullDVDefensivePower) ?? 0,
                    DVsAtHome         = playerVillages?.Sum(v => BattleSimulator.TotalDefensePower(v.ArmyAtHome) / (float)ArmyStats.FullDVDefensivePower) ?? 0,
                    DVsTraveling      = playerVillages?.Sum(v => BattleSimulator.TotalDefensePower(v.ArmyTraveling) / (float)ArmyStats.FullDVDefensivePower) ?? 0,
                    PopPerTribe       = supportByTargetTribe[player.PlayerId].Where(kvp => kvp.Value.Count > 0).ToDictionary(
                        kvp => tribeInfo[kvp.Key].Tag.UrlDecode(),
                        kvp => kvp.Value.Sum(a => BattleSimulator.TotalDefensePower(a) / (float)ArmyStats.FullDVDefensivePower)
                        )
                };

                result.Add(player.PlayerName.UrlDecode(), playerResult);
            }

            logger.LogDebug("Generated result data");

            return(result);
        }
Example #12
0
 public void CopyArmyStatsData(ArmyStats armyStats)
 {
     this.armyStats.hp     = armyStats.hp;
     this.armyStats.attack = armyStats.attack;
 }
Example #13
0
    IEnumerator FightRoutine(Collider collision)
    {
        ArmyStats myArmyBattleStats    = transform.GetComponent <ArmyStats>();
        ArmyStats enemyArmyBattleStats = collision.GetComponent <ArmyStats>();

        myArmyBattleStats.inBattle    = true;
        enemyArmyBattleStats.inBattle = true;

        // When the reinforcements hit the other team, you need to acces the one he is in battle with via this.
        myArmyBattleStats.inBattleWith    = enemyArmyBattleStats.gameObject;
        enemyArmyBattleStats.inBattleWith = myArmyBattleStats.gameObject;

        // Armies halt when they fight
        collision.GetComponent <MoveArmies>().StopRoutine();
        gameObject.GetComponent <MoveArmies>().StopRoutine();

        // Instantiate a timer
        gameObject.GetComponent <MoveArmies>().battleStartTime = Time.time;
        collision.GetComponent <MoveArmies>().battleStartTime  = Time.time;

        float myStrengthFactor = myArmyBattleStats.armyForce / (myArmyBattleStats.armyForce + enemyArmyBattleStats.armyForce);

        GameObject battle = Instantiate(battleGround, (transform.position + collision.transform.position) / 2, Quaternion.identity); // Takes the average of the 2 armies and places a battle prefab

        battle.transform.parent = GameObject.Find("_Dynamic").transform;                                                             // Becomes a dynamic object

        while (myArmyBattleStats.armySize >= 1 && enemyArmyBattleStats.armySize >= 1 && myArmyBattleStats.inBattle && enemyArmyBattleStats.inBattle)
        {
            battle.transform.Find("BattlegroundCanvas").Find("PlayerOneArmySizeText").gameObject.GetComponent <Text>().text = Mathf.RoundToInt(myArmyBattleStats.armySize).ToString();
            battle.transform.Find("BattlegroundCanvas").Find("PlayerTwoArmySizeText").gameObject.GetComponent <Text>().text = Mathf.RoundToInt(enemyArmyBattleStats.armySize).ToString();

            if (myStrengthFactor > Random.Range(0f, 1f)) // Strengthfactors decide the change of winning per time interval
            {
                enemyArmyBattleStats.armySize -= 0.2f;
            }
            else
            {
                myArmyBattleStats.armySize -= 0.2f;
            }
            myStrengthFactor = myArmyBattleStats.armyForce / (myArmyBattleStats.armyForce + enemyArmyBattleStats.armyForce);
            yield return(new WaitForSeconds(0.05f));
        }
        Destroy(battle);

        // The army that didnt flee gets a morale boost
        if (!enemyArmyBattleStats.inBattle && enemyArmyBattleStats.armySize >= 1)
        {
            myArmyBattleStats.moraleBoost += 0.1f;
        }
        else if (!myArmyBattleStats.inBattle && myArmyBattleStats.armySize >= 1)
        {
            enemyArmyBattleStats.moraleBoost += 0.1f;
        }

        myArmyBattleStats.inBattle        = false;
        enemyArmyBattleStats.inBattle     = false;
        myArmyBattleStats.inBattleWith    = null;
        enemyArmyBattleStats.inBattleWith = null;

        Destroy(battle);

        // When armysize is 0 the army is destroyed
        if (myArmyBattleStats.armySize < 1 && enemyArmyBattleStats.armySize < 1)
        {
            Destroy(gameObject);
            Destroy(collision.gameObject);
        }
        else if (myArmyBattleStats.armySize < 1)
        {
            enemyArmyBattleStats.moraleBoost += 0.2f;
            Destroy(gameObject);
        }
        else if (enemyArmyBattleStats.armySize < 1)
        {
            myArmyBattleStats.moraleBoost += 0.2f;
            Destroy(collision.gameObject);
        }
    }
 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
            }));
        }
        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));
        }