Пример #1
0
        //private


        //Runs in O(N log(N)) for all players
        private void UpdateRankings(IEnumerable <Player> players)
        {
            try
            {
                int currentDay = RatingSystems.ConvertDateToDays(DateTime.UtcNow);
                foreach (var p in players)
                {
                    if (p.days.Count == 0)
                    {
                        Trace.TraceError("WHR " + category + " has invalid player " + p.id + " with no days(games)");
                        continue;
                    }
                    float elo             = p.days.Last().getElo() + RatingOffset;
                    float lastUncertainty = p.days.Last().uncertainty * 100;
                    int   lastDay         = p.days.Last().day;
                    playerRatings[p.id] = new PlayerRating(int.MaxValue, 1, elo, lastUncertainty, lastDay, currentDay);
                    float rating = -playerRatings[p.id].Elo + 0.001f * (float)rand.NextDouble();
                    if (playerKeys.ContainsKey(p.id))
                    {
                        sortedPlayers.Remove(playerKeys[p.id]);
                    }
                    playerKeys[p.id]      = rating;
                    sortedPlayers[rating] = p.id;
                }
                float[] playerUncertainties = new float[playerRatings.Count];
                int     index = 0;
                float   DynamicMaxUncertainty = GlobalConst.MinimumDynamicMaxLadderUncertainty;
                int     maxAge = GlobalConst.LadderActivityDays;
                foreach (var pair in playerRatings)
                {
                    if (currentDay - pair.Value.LastGameDate > maxAge)
                    {
                        playerUncertainties[index++] = 9999 + index; //don't use infinity because i'm doing shady floating point things
                    }
                    else
                    {
                        playerUncertainties[index++] = (float)pair.Value.Uncertainty;
                    }
                }
                Array.Sort(playerUncertainties);
                DynamicMaxUncertainty = Math.Max(DynamicMaxUncertainty, playerUncertainties[Math.Min(playerUncertainties.Length, GlobalConst.LadderSize) - 1] + 0.01f);
                int          activePlayers         = Math.Max(1, ~Array.BinarySearch(playerUncertainties, DynamicMaxUncertainty));
                int          rank                  = 0;
                List <int>   newTopPlayers         = new List <int>();
                int          matched               = 0;
                List <float> newPercentileBrackets = new List <float>();
                newPercentileBrackets.Add(3000);
                float   percentile;
                float[] percentilesRev = Ranks.Percentiles.Reverse().ToArray();
                foreach (var pair in sortedPlayers)
                {
                    if (playerRatings[pair.Value].Uncertainty <= DynamicMaxUncertainty && currentDay - playerRatings[pair.Value].LastGameDate <= maxAge)
                    {
                        newTopPlayers.Add(pair.Value);
                        if (rank == matched && rank < topPlayers.Count && topPlayers[rank] == pair.Value)
                        {
                            matched++;
                        }
                        rank++;
                        percentile = (float)rank / activePlayers;
                        if (newPercentileBrackets.Count <= Ranks.Percentiles.Length && percentile > percentilesRev[newPercentileBrackets.Count - 1])
                        {
                            newPercentileBrackets.Add(playerRatings[pair.Value].Elo);
                        }
                        playerRatings[pair.Value].ApplyLadderUpdate(rank, percentile, currentDay);
                    }
                    else if (playerRatings[pair.Value].Rank < int.MaxValue)
                    {
                        playerRatings[pair.Value].ApplyLadderUpdate(int.MaxValue, 1, currentDay);
                    }
                }
                this.activePlayers = rank;
                newPercentileBrackets.Add(0);
                PercentileBrackets = newPercentileBrackets.Select(x => x).Reverse().ToArray();
                topPlayers         = newTopPlayers;
                laddersCache       = new List <Account>();
                Trace.TraceInformation("WHR " + category + " Ladders updated with " + topPlayers.Count + "/" + this.players.Count + " entries, max uncertainty selected: " + DynamicMaxUncertainty + " brackets are now: " + string.Join(", ", PercentileBrackets));

                var playerIds = players.Select(x => x.id).ToList();
                if (playerIds.Count() < 100)
                {
                    SaveToDB(playerIds);
                }
                else
                {
                    SaveToDB();
                }

                //check for rank updates

                List <int> playersWithRatingChange = new List <int>();
                using (var db = new ZkDataContext())
                {
                    var lastBattlePlayers = db.SpringBattlePlayers.Where(p => p.SpringBattleID == latestBattle.SpringBattleID && !p.IsSpectator).Include(x => x.Account).ToList();
                    if (latestBattle.GetRatingCategory() == category && lastBattleRanked)
                    {
                        lastBattleRanked = false;
                        lastBattlePlayers.Where(p => playerOldRatings.ContainsKey(RatingSystems.GetRatingId(p.AccountID)) && !p.EloChange.HasValue).ForEach(p =>
                        {
                            p.EloChange = playerRatings[RatingSystems.GetRatingId(p.AccountID)].RealElo - playerOldRatings[RatingSystems.GetRatingId(p.AccountID)].RealElo;
                        });
                        var updatedRanks = lastBattlePlayers.Where(p => Ranks.UpdateRank(p.Account, p.IsInVictoryTeam, !p.IsInVictoryTeam, db)).Select(x => x.Account).ToList();
                        updatedRanks.ForEach(p => db.Entry(p).State = EntityState.Modified);
                        playersWithRatingChange = lastBattlePlayers.Select(x => x.AccountID).ToList();
                    }
                    db.SpringBattlePlayers.Where(p => p.SpringBattleID == latestBattle.SpringBattleID && !p.IsSpectator).ToList().ForEach(x => playerOldRatings[RatingSystems.GetRatingId(x.AccountID)] = playerRatings[RatingSystems.GetRatingId(x.AccountID)]);
                    db.SaveChanges();
                }

                if (latestBattle.GetRatingCategory() == category && lastBattleRanked)
                {
                    //Publish new results only after saving new stats to db.
                    RatingsUpdated(this, new RatingUpdate()
                    {
                        affectedPlayers = playersWithRatingChange
                    });
                }


                //check for topX updates
                GetTopPlayers(GlobalConst.LadderSize);
                foreach (var listener in topPlayersUpdateListeners)
                {
                    if (matched < listener.Value)
                    {
                        listener.Key.TopPlayersUpdated(GetTopPlayers(listener.Value));
                    }
                }
            }
            catch (Exception ex)
            {
                string dbg = "WHR " + category + ": Failed to update rankings " + ex + "\nPlayers: ";
                foreach (var p in players)
                {
                    dbg += p.id + " (" + p.days.Count + " days), ";
                }
                Trace.TraceError(dbg);
            }
        }
Пример #2
0
        //private


        //Runs in O(N log(N)) for all players
        private void UpdateRankings(IEnumerable <Player> players)
        {
            try
            {
                Dictionary <int, float> oldRatings = new Dictionary <int, float>();

                //check for ladder elo updates
                using (var db = new ZkDataContext())
                {
                    var battleIDs         = pendingDebriefings.Keys.ToList();
                    var lastBattlePlayers = db.SpringBattlePlayers.Where(p => battleIDs.Contains(p.SpringBattleID) && !p.IsSpectator).Include(x => x.Account).ToList();
                    oldRatings = lastBattlePlayers.ToDictionary(p => p.AccountID, p => playerRatings[p.AccountID].LadderElo);
                    lastBattlePlayers.ForEach(p => playerRatings[p.AccountID].LadderElo = Ranks.UpdateLadderRating(p.Account, category, this.players[p.AccountID].avgElo + RatingOffset, p.IsInVictoryTeam, !p.IsInVictoryTeam, db));
                }

                //update ladders
                int currentDay  = RatingSystems.ConvertDateToDays(DateTime.UtcNow);
                int playerCount = 0;
                using (var db = new ZkDataContext())
                {
                    foreach (var p in players)
                    {
                        if (p.days.Count == 0)
                        {
                            Trace.TraceError("WHR " + category + " has invalid player " + p.id + " with no days(games)");
                            continue;
                        }
                        float elo = p.days.Last().GetElo() + RatingOffset;
                        float lastNaturalRatingVar = p.avgEloVar * GlobalConst.EloToNaturalRatingMultiplierSquared;
                        var   lastDay = p.days.Last();
                        float ladderElo;
                        if (playerRatings.ContainsKey(p.id))
                        {
                            ladderElo = playerRatings[p.id].LadderElo;
                        }
                        else
                        {
                            ladderElo = (float?)db.AccountRatings.Where(x => x.AccountID == p.id && x.RatingCategory == category).FirstOrDefault()?.LadderElo ?? DefaultRating.LadderElo;
                        }
                        playerRatings[p.id] = new PlayerRating(int.MaxValue, 1, elo, lastNaturalRatingVar, GlobalConst.NaturalRatingVariancePerDay(lastDay.totalWeight), lastDay.day, currentDay, ladderElo, !float.IsNaN(p.avgElo));
                        float rating = -playerRatings[p.id].LadderElo + 0.001f * (float)rand.NextDouble();
                        if (playerKeys.ContainsKey(p.id))
                        {
                            sortedPlayers.Remove(playerKeys[p.id]);
                        }
                        playerKeys[p.id]      = rating;
                        sortedPlayers[rating] = p.id;
                        if (playerRatings[p.id].Ranked)
                        {
                            playerCount++;
                        }
                    }
                }
                this.activePlayers = playerCount;
                int          rank                  = 0;
                List <int>   newTopPlayers         = new List <int>();
                int          matched               = 0;
                List <float> newPercentileBrackets = new List <float>();
                newPercentileBrackets.Add(playerRatings[sortedPlayers.First().Value].LadderElo + 420);
                float   percentile;
                float[] percentilesRev = Ranks.Percentiles.Reverse().ToArray();
                foreach (var pair in sortedPlayers)
                {
                    if (playerRatings[pair.Value].Ranked)
                    {
                        newTopPlayers.Add(pair.Value);
                        if (rank == matched && rank < topPlayers.Count && topPlayers[rank] == pair.Value)
                        {
                            matched++;
                        }
                        rank++;
                        percentile = (float)rank / activePlayers;
                        if (newPercentileBrackets.Count <= Ranks.Percentiles.Length && percentile > percentilesRev[newPercentileBrackets.Count - 1])
                        {
                            newPercentileBrackets.Add(playerRatings[pair.Value].LadderElo);
                        }
                        playerRatings[pair.Value].ApplyLadderUpdate(rank, percentile, currentDay, true);
                    }
                    else if (playerRatings[pair.Value].Rank < int.MaxValue)
                    {
                        playerRatings[pair.Value].ApplyLadderUpdate(int.MaxValue, 1, currentDay, false);
                    }
                }
                newPercentileBrackets.Add(newPercentileBrackets.Last() - 420);
                PercentileBrackets = newPercentileBrackets.Select(x => x).Reverse().ToArray();
                topPlayers         = newTopPlayers;
                laddersCache       = new List <Account>();
                Trace.TraceInformation("WHR " + category + " Ladders updated with " + topPlayers.Count + "/" + this.players.Count + " entries. Brackets are now: " + string.Join(", ", PercentileBrackets));

                var playerIds = players.Select(x => x.id).ToList();
                if (playerIds.Count() < 100)
                {
                    SaveToDB(playerIds);
                }
                else
                {
                    SaveToDB();
                }

                //check for rank updates

                if (pendingDebriefings.Any())
                {
                    List <int>                playersWithRatingChange = new List <int>();
                    Dictionary <int, int>     oldRanks         = new Dictionary <int, int>();
                    Dictionary <int, Account> updatedRanks     = new Dictionary <int, Account>();
                    Dictionary <int, Account> involvedAccounts = new Dictionary <int, Account>();
                    Trace.TraceInformation("WHR Filling in Debriefings for Battles: " + pendingDebriefings.Keys.Select(x => "B" + x).StringJoin());
                    using (var db = new ZkDataContext())
                    {
                        var battleIDs         = pendingDebriefings.Keys.ToList();
                        var lastBattlePlayers = db.SpringBattlePlayers.Where(p => battleIDs.Contains(p.SpringBattleID) && !p.IsSpectator).Include(x => x.Account).ToList();
                        involvedAccounts = lastBattlePlayers.ToDictionary(p => p.AccountID, p => p.Account);
                        Trace.TraceInformation("WHR Debriefing players: " + involvedAccounts.Values.Select(x => x.Name).StringJoin());
                        oldRanks     = lastBattlePlayers.ToDictionary(p => p.AccountID, p => p.Account.Rank);
                        updatedRanks = lastBattlePlayers.Where(p => Ranks.UpdateRank(p.Account, p.IsInVictoryTeam, !p.IsInVictoryTeam, db)).Select(x => x.Account).ToDictionary(p => p.AccountID, p => p);
                        updatedRanks.Values.ForEach(p => db.Entry(p).State = EntityState.Modified);
                        playersWithRatingChange = lastBattlePlayers.Select(x => x.AccountID).ToList();

                        lastBattlePlayers.Where(p => playerOldRatings.ContainsKey(RatingSystems.GetRatingId(p.AccountID)) && !p.EloChange.HasValue).ForEach(p =>
                        {
                            //p.EloChange = playerRatings[RatingSystems.GetRatingId(p.AccountID)].RealElo - playerOldRatings[RatingSystems.GetRatingId(p.AccountID)].RealElo;
                            p.EloChange = playerRatings[p.AccountID].LadderElo - oldRatings[p.AccountID];
                        });

                        db.SpringBattlePlayers.Where(p => battleIDs.Contains(p.SpringBattleID) && !p.IsSpectator).ToList().ForEach(x => playerOldRatings[RatingSystems.GetRatingId(x.AccountID)] = playerRatings[RatingSystems.GetRatingId(x.AccountID)]);
                        db.SaveChanges();
                    }
                    //Publish new results only after saving new stats to db.
                    pendingDebriefings.ForEach(pair =>
                    {
                        pair.Value.partialDebriefing.DebriefingUsers.Values.ForEach(user => {
                            try
                            {
                                user.EloChange  = playerRatings[user.AccountID].LadderElo - oldRatings[user.AccountID];
                                user.IsRankup   = updatedRanks.ContainsKey(user.AccountID) && oldRanks[user.AccountID] < updatedRanks[user.AccountID].Rank;
                                user.IsRankdown = updatedRanks.ContainsKey(user.AccountID) && oldRanks[user.AccountID] > updatedRanks[user.AccountID].Rank;
                                var prog        = Ranks.GetRankProgress(involvedAccounts[user.AccountID], this);
                                if (prog == null)
                                {
                                    Trace.TraceWarning("User " + user.AccountID + " is wrongfully unranked");
                                }
                                user.NextRankElo = prog.RankCeilElo;
                                user.PrevRankElo = prog.RankFloorElo;
                                user.NewElo      = prog.CurrentElo;
                            }
                            catch (Exception ex)
                            {
                                Trace.TraceError("Unable to complete debriefing for user " + user.AccountID + ": " + ex);
                            }
                        });
                        pair.Value.partialDebriefing.RatingCategory = category.ToString();
                        pair.Value.debriefingConsumer.Invoke(pair.Value.partialDebriefing);
                    });
                    RatingsUpdated(this, new RatingUpdate()
                    {
                        affectedPlayers = playersWithRatingChange
                    });
                    pendingDebriefings.Clear();
                }


                //check for topX updates
                GetTopPlayers(GlobalConst.LadderSize);
                foreach (var listener in topPlayersUpdateListeners)
                {
                    if (matched < listener.Value)
                    {
                        listener.Key.TopPlayersUpdated(GetTopPlayers(listener.Value));
                    }
                }
            }
            catch (Exception ex)
            {
                string dbg = "WHR " + category + ": Failed to update rankings " + ex + "\nPlayers: ";
                foreach (var p in players)
                {
                    dbg += p.id + " (" + p.days.Count + " days), ";
                }
                Trace.TraceError(dbg);
            }
        }
        public void UpdateRatings()
        {
            if (!RatingSystems.Initialized)
            {
                return;
            }
            if (latestBattle == null)
            {
                //Trace.TraceInformation("WHR " + category +": No battles to evaluate");
                return;
            }
            lock (updateLock)
            {
                Action updateAction = null;
                if (lastUpdate == null)
                {
                    updateAction = (() =>
                    {
                        Trace.TraceInformation("Initializing WHR " + category + " ratings for " + battlesRegistered + " battles, this will take some time.. From B" + firstBattle?.SpringBattleID + " to B" + latestBattle?.SpringBattleID);
                        runIterations(75);
                        UpdateRankings(players.Values);
                        playerOldRatings = new Dictionary <int, PlayerRating>(playerRatings);
                    });
                }
                else if (DateTime.UtcNow.Subtract(lastUpdateTime).TotalHours >= GlobalConst.LadderUpdatePeriod)
                {
                    updateAction = (() =>
                    {
                        Trace.TraceInformation("Updating all WHR " + category + " ratings");
                        runIterations(1);
                        UpdateRankings(players.Values);
                    });
                    lastUpdateTime = DateTime.UtcNow;
                }
                else if (!latestBattle.Equals(lastUpdate))
                {
                    updateAction = (() =>
                    {
                        Trace.TraceInformation("Updating WHR " + category + " ratings for last Battle: " + latestBattle.SpringBattleID);
                        IEnumerable <Player> players = latestBattle.SpringBattlePlayers.Where(p => !p.IsSpectator).Select(p => getPlayerById(RatingSystems.GetRatingId(p.AccountID)));
                        players.ForEach(p => p.runOneNewtonIteration());
                        players.ForEach(p => p.updateUncertainty());
                        UpdateRankings(players);
                    });
                }
                else
                {
                    //Trace.TraceInformation("No WHR " + category +" ratings to update");
                    return;
                }
                var lastUpdateEx = lastUpdate;
                Task.Factory.StartNew(() =>
                {
                    try
                    {
                        lock (updateLockInternal)
                        {
                            DateTime start = DateTime.Now;
                            updateAction.Invoke();
                            Trace.TraceInformation("WHR " + category + " Ratings updated in " + DateTime.Now.Subtract(start).TotalSeconds + " seconds, " + (GC.GetTotalMemory(false) / (1 << 20)) + "MiB total memory allocated");

                            IEnumerable <Account> updatedRanks = new List <Account>();
                            using (var db = new ZkDataContext())
                            {
                                var lastBattlePlayers = db.SpringBattlePlayers.Where(p => p.SpringBattleID == latestBattle.SpringBattleID && !p.IsSpectator).Include(x => x.Account).ToList();
                                if (latestBattle.GetRatingCategory() == category && lastBattleRanked)
                                {
                                    lastBattleRanked = false;
                                    lastBattlePlayers.Where(p => playerOldRatings.ContainsKey(RatingSystems.GetRatingId(p.AccountID)) && !p.EloChange.HasValue).ForEach(p =>
                                    {
                                        p.EloChange = playerRatings[RatingSystems.GetRatingId(p.AccountID)].RealElo - playerOldRatings[RatingSystems.GetRatingId(p.AccountID)].RealElo;
                                    });
                                    updatedRanks = lastBattlePlayers.Where(p => Ranks.UpdateRank(p.Account, p.IsInVictoryTeam, !p.IsInVictoryTeam, db)).Select(x => x.Account).ToList();
                                    updatedRanks.ForEach(p => db.Entry(p).State = EntityState.Modified);
                                }
                                db.SpringBattlePlayers.Where(p => p.SpringBattleID == latestBattle.SpringBattleID && !p.IsSpectator).ToList().ForEach(x => playerOldRatings[RatingSystems.GetRatingId(x.AccountID)] = playerRatings[RatingSystems.GetRatingId(x.AccountID)]);
                                db.SaveChanges();
                            }
                            RatingsUpdated(this, new RatingUpdate()
                            {
                                affectedPlayers = updatedRanks.Select(p => RatingSystems.GetRatingId(p.AccountID))
                            });
                        }
                    }
                    catch (Exception ex)
                    {
                        Trace.TraceError("Thread error while updating WHR " + category + " " + ex);
                    }
                }, CancellationToken.None, TaskCreationOptions.None, PriorityScheduler.BelowNormal);
                lastUpdate = latestBattle;
            }
        }
Пример #4
0
        //private


        //Runs in O(N log(N)) for all players
        private void UpdateRankings(IEnumerable <Player> players)
        {
            var debriefings = new Dictionary <int, PendingDebriefing>(pendingDebriefings);
            int matched     = 0;

            try
            {
                //check for ladder elo updates
                using (var db = new ZkDataContext())
                {
                    var battleIDs = debriefings.Keys.ToList();
                    foreach (var battleId in debriefings.Keys)
                    {
                        List <SpringBattlePlayer> lastBattlePlayers = db.SpringBattlePlayers.Where(p => p.SpringBattleID == battleId && !p.IsSpectator).Include(x => x.Account).DistinctBy(x => x.AccountID).ToList();
                        Dictionary <int, float>   oldRatings        = lastBattlePlayers.ToDictionary(p => (p.AccountID), p => GetPlayerRating(p.AccountID).LadderElo);
                        lastBattlePlayers.Where(p => !playerRatings.ContainsKey((p.AccountID))).ForEach(p => playerRatings[(p.AccountID)] = new PlayerRating(DefaultRating));
                        Dictionary <int, float> winChances = db.SpringBattles.Where(p => p.SpringBattleID == battleId).First().GetAllyteamWinChances();
                        lastBattlePlayers.ForEach(p => {
                            float eloChange = (p.IsInVictoryTeam ? (1f - winChances[p.AllyNumber]) : (-winChances[p.AllyNumber])) * GlobalConst.LadderEloClassicEloK / lastBattlePlayers.Count(x => x.AllyNumber == p.AllyNumber);
                            playerRatings[p.AccountID].LadderElo = Ranks.UpdateLadderRating(p.Account, category, getPlayerById(p.AccountID).avgElo + RatingOffset, p.IsInVictoryTeam, !p.IsInVictoryTeam, eloChange, db);
                        });
                        lastBattlePlayers.Where(p => !p.EloChange.HasValue).ForEach(p =>
                        {
                            p.EloChange = playerRatings[(p.AccountID)].LadderElo - oldRatings[(p.AccountID)];
                            db.SpringBattlePlayers.Attach(p);
                            db.Entry(p).Property(x => x.EloChange).IsModified = true;
                        });
                        db.SaveChanges();
                    }
                }

                //update ladders
                int currentDay  = RatingSystems.ConvertDateToDays(DateTime.UtcNow);
                int playerCount = 0;
                using (var db = new ZkDataContext())
                {
                    foreach (var p in players)
                    {
                        if (p.days.Count == 0)
                        {
                            Trace.TraceError("WHR " + category + " has invalid player " + p.id + " with no days(games)");
                            continue;
                        }
                        float elo = p.days.Last().GetElo() + RatingOffset;
                        float lastNaturalRatingVar = p.days.Last().naturalRatingVariance;
                        var   lastDay = p.days.Last();
                        float ladderElo;
                        if (playerRatings.ContainsKey(p.id))
                        {
                            ladderElo = playerRatings[p.id].LadderElo;
                        }
                        else
                        {
                            ladderElo = (float?)db.AccountRatings.Where(x => x.AccountID == p.id && x.RatingCategory == category).FirstOrDefault()?.LadderElo ?? DefaultRating.LadderElo;
                        }
                        playerRatings[p.id] = new PlayerRating(int.MaxValue, 1, elo, lastNaturalRatingVar, GlobalConst.NaturalRatingVariancePerDay(lastDay.totalWeight), lastDay.day, currentDay, ladderElo, !float.IsNaN(p.avgElo));
                        float rating = -playerRatings[p.id].LadderElo;
                        if (playerKeys.ContainsKey(p.id))
                        {
                            sortedPlayers.Remove(playerKeys[p.id]);
                        }
                        while (sortedPlayers.ContainsKey(rating))
                        {
                            rating += 0.01f;
                        }
                        playerKeys[p.id]      = rating;
                        sortedPlayers[rating] = p.id;
                        if (playerRatings[p.id].Ranked)
                        {
                            playerCount++;
                        }
                    }
                }
                this.activePlayers = playerCount;
                int          rank                  = 0;
                List <int>   newTopPlayers         = new List <int>();
                List <float> newPercentileBrackets = new List <float>();
                newPercentileBrackets.Add(playerRatings[sortedPlayers.First().Value].LadderElo);
                float   percentile;
                float[] percentilesRev = Ranks.Percentiles.Reverse().ToArray();
                foreach (var pair in sortedPlayers)
                {
                    if (playerRatings[pair.Value].Ranked)
                    {
                        newTopPlayers.Add(pair.Value);
                        if (rank == matched && rank < topPlayers.Count && topPlayers[rank] == pair.Value)
                        {
                            matched++;
                        }
                        rank++;
                        percentile = (float)rank / activePlayers;
                        if (newPercentileBrackets.Count <= Ranks.Percentiles.Length && percentile > percentilesRev[newPercentileBrackets.Count - 1])
                        {
                            newPercentileBrackets.Add(playerRatings[pair.Value].LadderElo);
                        }
                        playerRatings[pair.Value].ApplyLadderUpdate(rank, percentile, currentDay, true);
                    }
                    else if (playerRatings[pair.Value].Rank < int.MaxValue)
                    {
                        playerRatings[pair.Value].ApplyLadderUpdate(int.MaxValue, 1, currentDay, false);
                    }
                }
                if (rank != playerCount)
                {
                    Trace.TraceWarning("WHR has " + playerCount + " active players, but " + rank + " sorted active players");
                }
                while (newPercentileBrackets.Count < Ranks.Percentiles.Length + 1)
                {
                    newPercentileBrackets.Add(playerRatings[sortedPlayers.Last().Value].LadderElo);
                }
                PercentileBrackets = newPercentileBrackets.Select(x => x).Reverse().ToArray();
                topPlayers         = newTopPlayers;
                laddersCache       = new List <Account>();
                Trace.TraceInformation("WHR " + category + " Ladders updated with " + topPlayers.Count + "/" + this.players.Count + " entries. Brackets are now: " + string.Join(", ", PercentileBrackets));

                var playerIds = players.Select(x => x.id).ToList();
                if (playerIds.Count() < 100)
                {
                    SaveToDB(playerIds);
                }
                else
                {
                    SaveToDB();
                }
            }
            catch (Exception ex)
            {
                Trace.TraceError("WHR " + category + ": Failed to update rankings: " + ex);
                PendingDebriefing discard2;
                debriefings.ForEach(x => pendingDebriefings.TryRemove(x.Key, out discard2));
                return;
            }
            try
            {
                //check for rank updates
                if (debriefings.Any())
                {
                    Trace.TraceInformation("WHR Filling in Debriefings for Battles: " + debriefings.Keys.Select(x => "B" + x).StringJoin());
                    using (var db = new ZkDataContext())
                    {
                        foreach (var battleId in debriefings.Keys)
                        {
                            List <SpringBattlePlayer>            lastBattlePlayers = db.SpringBattlePlayers.Where(p => p.SpringBattleID == battleId && !p.IsSpectator).Include(x => x.Account).DistinctBy(x => x.AccountID).ToList();
                            Dictionary <int, SpringBattlePlayer> involvedPlayers   = lastBattlePlayers.ToDictionary(p => p.AccountID, p => p);
                            Trace.TraceInformation("WHR Debriefing players: " + involvedPlayers.Values.Select(x => x.Account.Name).StringJoin());
                            Dictionary <int, int>     oldRanks     = lastBattlePlayers.ToDictionary(p => p.AccountID, p => p.Account.Rank);
                            Dictionary <int, Account> updatedRanks = lastBattlePlayers.Where(p => Ranks.UpdateRank(p.Account, p.IsInVictoryTeam, !p.IsInVictoryTeam, db)).Select(x => x.Account).ToDictionary(p => p.AccountID, p => p);
                            updatedRanks.Values.ForEach(p =>
                            {
                                db.Accounts.Attach(p);
                                db.Entry(p).Property(x => x.Rank).IsModified = true;
                            });
                            List <int> playersWithRatingChange = lastBattlePlayers.Select(x => x.AccountID).ToList();
                            db.SaveChanges();

                            //Publish new results only after saving new stats to db.
                            debriefings[battleId].partialDebriefing.DebriefingUsers.Values.ForEach(user =>
                            {
                                try
                                {
                                    user.EloChange  = involvedPlayers[user.AccountID].EloChange ?? 0;
                                    user.IsRankup   = updatedRanks.ContainsKey(user.AccountID) && oldRanks[user.AccountID] < updatedRanks[user.AccountID].Rank;
                                    user.IsRankdown = updatedRanks.ContainsKey(user.AccountID) && oldRanks[user.AccountID] > updatedRanks[user.AccountID].Rank;
                                    var prog        = Ranks.GetRankProgress(involvedPlayers[user.AccountID].Account, this);
                                    if (prog == null)
                                    {
                                        Trace.TraceWarning("User " + user.AccountID + " is wrongfully unranked");
                                    }
                                    user.NextRankElo = prog.RankCeilElo;
                                    user.PrevRankElo = prog.RankFloorElo;
                                    user.NewElo      = prog.CurrentElo;
                                }
                                catch (Exception ex)
                                {
                                    Trace.TraceError("Unable to complete debriefing for user " + user.AccountID + ": " + ex);
                                }
                            });
                            debriefings[battleId].partialDebriefing.RatingCategory = category.ToString();
                            debriefings[battleId].debriefingConsumer.Invoke(debriefings[battleId].partialDebriefing);
                            RatingsUpdated(this, new RatingUpdate()
                            {
                                affectedPlayers = playersWithRatingChange
                            });
                        }
                    }
                }

                //check for topX updates
                GetTopPlayers(GlobalConst.LadderSize);
                foreach (var listener in topPlayersUpdateListeners)
                {
                    if (matched < listener.Value)
                    {
                        listener.Key.TopPlayersUpdated(GetTopPlayers(listener.Value));
                    }
                }
            }
            catch (Exception ex)
            {
                Trace.TraceError("WHR " + category + ": Failed to process battles for rankings: " + ex);
            }
            PendingDebriefing discard;

            debriefings.ForEach(x => pendingDebriefings.TryRemove(x.Key, out discard));
        }