/// <summary> /// Creates and caches score containers for /// </summary> private void CacheMultiplayerScoreContainers() { if (MultiplayerScores == null) { return; } CachedScoreContainers = new Dictionary <ScoreboardUser, ResultScoreContainer>(); var self = MultiplayerScores.Find(x => x.Type == ScoreboardUserType.Self); var view = (ResultScreenView)View; CachedScoreContainers.Add(self, view.ScoreContainer); foreach (var s in MultiplayerScores) { if (s.Type == ScoreboardUserType.Self) { continue; } ScoreProcessor = s.Processor; CachedScoreContainers.Add(s, new ResultScoreContainer(this) { Parent = view.MainContainer, Visible = s.Type == ScoreboardUserType.Self }); } ScoreProcessor = self.Processor; }
private void addCursor(MultiplayerScores scores) { scores.Cursor = new Cursor { Properties = new Dictionary <string, JToken> { { "total_score", JToken.FromObject(scores.Scores[^ 1].TotalScore) },
/// <summary> /// Creates a <see cref="IndexPlaylistScoresRequest"/> with an optional score pivot. /// </summary> /// <remarks>Does not queue the request.</remarks> /// <param name="scoresCallback">The callback to perform with the resulting scores.</param> /// <param name="pivot">An optional score pivot to retrieve scores around. Can be null to retrieve scores from the highest score.</param> /// <returns>The indexing <see cref="APIRequest"/>.</returns> private APIRequest createIndexRequest(Action <IEnumerable <ScoreInfo> > scoresCallback, [CanBeNull] MultiplayerScores pivot = null) { var indexReq = pivot != null ? new IndexPlaylistScoresRequest(roomId, playlistItem.ID, pivot.Cursor, pivot.Params) : new IndexPlaylistScoresRequest(roomId, playlistItem.ID); indexReq.Success += r => { if (pivot == lowerScores) { lowerScores = r; setPositions(r, pivot, 1); } else { higherScores = r; setPositions(r, pivot, -1); } performSuccessCallback(scoresCallback, r.Scores, r); }; indexReq.Failure += _ => hideLoadingSpinners(pivot); return(indexReq); }
private void hideLoadingSpinners([CanBeNull] MultiplayerScores pivot = null) { CentreSpinner.Hide(); if (pivot == lowerScores) { RightSpinner.Hide(); } else if (pivot == higherScores) { LeftSpinner.Hide(); } }
protected override APIRequest FetchScores(Action <IEnumerable <ScoreInfo> > scoresCallback) { // This performs two requests: // 1. A request to show the user's score (and scores around). // 2. If that fails, a request to index the room starting from the highest score. var userScoreReq = new ShowPlaylistUserScoreRequest(roomId, playlistItem.ID, api.LocalUser.Value.Id); userScoreReq.Success += userScore => { var allScores = new List <MultiplayerScore> { userScore }; // Other scores could have arrived between score submission and entering the results screen. Ensure the local player score position is up to date. if (Score != null) { Score.Position = userScore.Position; ScorePanelList.GetPanelForScore(Score).ScorePosition.Value = userScore.Position; } if (userScore.ScoresAround?.Higher != null) { allScores.AddRange(userScore.ScoresAround.Higher.Scores); higherScores = userScore.ScoresAround.Higher; Debug.Assert(userScore.Position != null); setPositions(higherScores, userScore.Position.Value, -1); } if (userScore.ScoresAround?.Lower != null) { allScores.AddRange(userScore.ScoresAround.Lower.Scores); lowerScores = userScore.ScoresAround.Lower; Debug.Assert(userScore.Position != null); setPositions(lowerScores, userScore.Position.Value, 1); } performSuccessCallback(scoresCallback, allScores); }; // On failure, fallback to a normal index. userScoreReq.Failure += _ => api.Queue(createIndexRequest(scoresCallback)); return(userScoreReq); }
/// <summary> /// Changes discord rich presence to show results. /// </summary> private void ChangeDiscordPresence() { DiscordHelper.Presence.EndTimestamp = 0; // Don't change if we're loading in from a replay file. if (ResultsType == ResultScreenType.Replay || Gameplay.InReplayMode) { DiscordHelper.Presence.Details = "Idle"; DiscordHelper.Presence.State = "In the Menus"; DiscordRpc.UpdatePresence(ref DiscordHelper.Presence); return; } var state = Gameplay.Failed ? "Fail" : "Pass"; var score = $"{ScoreProcessor.Score / 1000}k"; var acc = $"{StringHelper.AccuracyToString(ScoreProcessor.Accuracy)}"; var grade = Gameplay.Failed ? "F" : GradeHelper.GetGradeFromAccuracy(ScoreProcessor.Accuracy).ToString(); var combo = $"{ScoreProcessor.MaxCombo}x"; if (OnlineManager.CurrentGame == null) { DiscordHelper.Presence.State = $"{state}: {grade} {score} {acc} {combo}"; } else { if (OnlineManager.CurrentGame.Ruleset == MultiplayerGameRuleset.Team) { var redTeamAverage = GetTeamAverage(MultiplayerTeam.Red); var blueTeamAverage = GetTeamAverage(MultiplayerTeam.Blue); DiscordHelper.Presence.State = $"Red: {redTeamAverage:0.00} vs. Blue: {blueTeamAverage:0.00}"; } else { DiscordHelper.Presence.State = $"{StringHelper.AddOrdinal(MultiplayerScores.First().Rank)} " + $"Place: {MultiplayerScores.First().RatingProcessor.CalculateRating(ScoreProcessor):0.00} {acc} {grade}"; } } DiscordRpc.UpdatePresence(ref DiscordHelper.Presence); }
/// <summary> /// Gets the average rating of an individual team /// </summary> /// <param name="team"></param> /// <returns></returns> /// <exception cref="ArgumentOutOfRangeException"></exception> public double GetTeamAverage(MultiplayerTeam team) { List <ScoreboardUser> users; switch (team) { case MultiplayerTeam.Red: users = MultiplayerScores.FindAll(x => x.Scoreboard.Team == MultiplayerTeam.Red && !x.HasQuit); break; case MultiplayerTeam.Blue: users = MultiplayerScores.FindAll(x => x.Scoreboard.Team == MultiplayerTeam.Blue && !x.HasQuit); break; default: throw new ArgumentOutOfRangeException(nameof(team), team, null); } if (users.Count == 0) { return(0); } var sum = 0d; users.ForEach(x => { var rating = x.RatingProcessor.CalculateRating(x.Processor); if (x.Processor.MultiplayerProcessor.IsEliminated || x.Processor.MultiplayerProcessor.IsRegeneratingHealth) { rating = 0; } sum += rating; }); return(sum / users.Count); }
protected override APIRequest FetchNextPage(int direction, Action <IEnumerable <ScoreInfo> > scoresCallback) { Debug.Assert(direction == 1 || direction == -1); MultiplayerScores pivot = direction == -1 ? higherScores : lowerScores; if (pivot?.Cursor == null) { return(null); } if (pivot == higherScores) { LeftSpinner.Show(); } else { RightSpinner.Show(); } return(createIndexRequest(scoresCallback, pivot)); }
/// <summary> /// Applies positions to all <see cref="MultiplayerScore"/>s referenced to a given pivot. /// </summary> /// <param name="scores">The <see cref="MultiplayerScores"/> to set positions on.</param> /// <param name="pivot">The pivot.</param> /// <param name="increment">The amount to increment the pivot position by for each <see cref="MultiplayerScore"/> in <paramref name="scores"/>.</param> private void setPositions([NotNull] MultiplayerScores scores, [CanBeNull] MultiplayerScores pivot, int increment) => setPositions(scores, pivot?.Scores[^ 1].Position ?? 0, increment);
/// <summary> /// Transforms returned <see cref="MultiplayerScores"/> into <see cref="ScoreInfo"/>s, ensure the <see cref="ScorePanelList"/> is put into a sane state, and invokes a given success callback. /// </summary> /// <param name="callback">The callback to invoke with the final <see cref="ScoreInfo"/>s.</param> /// <param name="scores">The <see cref="MultiplayerScore"/>s that were retrieved from <see cref="APIRequest"/>s.</param> /// <param name="pivot">An optional pivot around which the scores were retrieved.</param> private void performSuccessCallback([NotNull] Action <IEnumerable <ScoreInfo> > callback, [NotNull] List <MultiplayerScore> scores, [CanBeNull] MultiplayerScores pivot = null) { var scoreInfos = scores.Select(s => s.CreateScoreInfo(playlistItem)).ToArray(); // Score panels calculate total score before displaying, which can take some time. In order to count that calculation as part of the loading spinner display duration, // calculate the total scores locally before invoking the success callback. scoreManager.OrderByTotalScoreAsync(scoreInfos).ContinueWith(_ => Schedule(() => { // Select a score if we don't already have one selected. // Note: This is done before the callback so that the panel list centres on the selected score before panels are added (eliminating initial scroll). if (SelectedScore.Value == null) { Schedule(() => { // Prefer selecting the local user's score, or otherwise default to the first visible score. SelectedScore.Value = scoreInfos.FirstOrDefault(s => s.User.Id == api.LocalUser.Value.Id) ?? scoreInfos.FirstOrDefault(); }); } // Invoke callback to add the scores. Exclude the user's current score which was added previously. callback.Invoke(scoreInfos.Where(s => s.OnlineScoreID != Score?.OnlineScoreID)); hideLoadingSpinners(pivot); })); }
/// <summary> /// Transforms returned <see cref="MultiplayerScores"/> into <see cref="ScoreInfo"/>s, ensure the <see cref="ScorePanelList"/> is put into a sane state, and invokes a given success callback. /// </summary> /// <param name="callback">The callback to invoke with the final <see cref="ScoreInfo"/>s.</param> /// <param name="scores">The <see cref="MultiplayerScore"/>s that were retrieved from <see cref="APIRequest"/>s.</param> /// <param name="pivot">An optional pivot around which the scores were retrieved.</param> private void performSuccessCallback([NotNull] Action <IEnumerable <ScoreInfo> > callback, [NotNull] List <MultiplayerScore> scores, [CanBeNull] MultiplayerScores pivot = null) { var scoreInfos = new List <ScoreInfo>(scores.Select(s => s.CreateScoreInfo(playlistItem))); // Select a score if we don't already have one selected. // Note: This is done before the callback so that the panel list centres on the selected score before panels are added (eliminating initial scroll). if (SelectedScore.Value == null) { Schedule(() => { // Prefer selecting the local user's score, or otherwise default to the first visible score. SelectedScore.Value = scoreInfos.FirstOrDefault(s => s.User.Id == api.LocalUser.Value.Id) ?? scoreInfos.FirstOrDefault(); }); } // Invoke callback to add the scores. Exclude the user's current score which was added previously. callback.Invoke(scoreInfos.Where(s => s.OnlineScoreID != Score?.OnlineScoreID)); hideLoadingSpinners(pivot); }