//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); } }
//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; } }
//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)); }