1
0
mirror of https://github.com/ppy/osu.git synced 2025-01-13 02:32:55 +08:00

Add results screen for displaying arbitrary daily challenge scores

At this point its primary usage is the daily challenge event feed, but
the leaderboard will be using this too shortly.

Because the playlists results screen that exists in `master` is
hard-coupled to showing the *local user's* best result on a given
playlist by way of hard-coupling itself to the relevant API request,
allowing show of *arbitrary* score by ID requires a whole bunch of
subclassery as things stand. Oh well.

Class naming is... best effort, due to the above.
This commit is contained in:
Bartłomiej Dach 2024-06-06 10:42:09 +02:00
parent df97215298
commit b29e535ca5
No known key found for this signature in database
10 changed files with 152 additions and 39 deletions

View File

@ -413,7 +413,7 @@ namespace osu.Game.Tests.Visual.Playlists
};
}
private partial class TestResultsScreen : PlaylistsResultsScreen
private partial class TestResultsScreen : PlaylistItemUserResultsScreen
{
public new LoadingSpinner LeftSpinner => base.LeftSpinner;
public new LoadingSpinner CentreSpinner => base.CentreSpinner;

View File

@ -0,0 +1,23 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Game.Online.API;
namespace osu.Game.Online.Rooms
{
public class ShowPlaylistScoreRequest : APIRequest<MultiplayerScore>
{
private readonly long roomId;
private readonly long playlistItemId;
private readonly long scoreId;
public ShowPlaylistScoreRequest(long roomId, long playlistItemId, long scoreId)
{
this.roomId = roomId;
this.playlistItemId = playlistItemId;
this.scoreId = scoreId;
}
protected override string Target => $@"rooms/{roomId}/playlist/{playlistItemId}/scores/{scoreId}";
}
}

View File

@ -209,6 +209,11 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge
feed = new DailyChallengeEventFeed
{
RelativeSizeAxes = Axes.Both,
PresentScore = id =>
{
if (this.IsCurrentScreen())
this.Push(new PlaylistItemScoreResultsScreen(room.RoomID.Value!.Value, playlistItem, id));
}
}
],
},

View File

@ -1,6 +1,7 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using System.Collections.Generic;
using System.Linq;
using osu.Framework.Allocation;
@ -19,6 +20,8 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge
{
private DailyChallengeEventFeedFlow flow = null!;
public Action<long>? PresentScore { get; init; }
[BackgroundDependencyLoader]
private void load()
{
@ -48,6 +51,7 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge
{
Anchor = Anchor.BottomCentre,
Origin = Anchor.BottomCentre,
PresentScore = PresentScore,
};
flow.Add(row);
row.Delay(15000).Then().FadeOut(300, Easing.OutQuint).Expire();
@ -78,6 +82,8 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge
{
private readonly NewScoreEvent newScore;
public Action<long>? PresentScore { get; init; }
public NewScoreEventRow(NewScoreEvent newScore)
{
this.newScore = newScore;
@ -115,7 +121,7 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge
text.AddUserLink(newScore.User);
text.AddText(" got ");
text.AddLink($"{newScore.TotalScore:N0} points", () => { }); // TODO: present the score here
text.AddLink($"{newScore.TotalScore:N0} points", () => PresentScore?.Invoke(newScore.ScoreID));
if (newScore.NewRank != null)
text.AddText($" and achieved rank #{newScore.NewRank.Value:N0}");

View File

@ -7,7 +7,7 @@ using osu.Game.Screens.OnlinePlay.Playlists;
namespace osu.Game.Screens.OnlinePlay.Multiplayer
{
public partial class MultiplayerResultsScreen : PlaylistsResultsScreen
public partial class MultiplayerResultsScreen : PlaylistItemUserResultsScreen
{
public MultiplayerResultsScreen(ScoreInfo score, long roomId, PlaylistItem playlistItem)
: base(score, roomId, playlistItem)

View File

@ -17,10 +17,10 @@ using osu.Game.Screens.Ranking;
namespace osu.Game.Screens.OnlinePlay.Playlists
{
public partial class PlaylistsResultsScreen : ResultsScreen
public abstract partial class PlaylistItemResultsScreen : ResultsScreen
{
private readonly long roomId;
private readonly PlaylistItem playlistItem;
protected readonly long RoomId;
protected readonly PlaylistItem PlaylistItem;
protected LoadingSpinner LeftSpinner { get; private set; } = null!;
protected LoadingSpinner CentreSpinner { get; private set; } = null!;
@ -30,19 +30,19 @@ namespace osu.Game.Screens.OnlinePlay.Playlists
private MultiplayerScores? lowerScores;
[Resolved]
private IAPIProvider api { get; set; } = null!;
protected IAPIProvider API { get; private set; } = null!;
[Resolved]
private ScoreManager scoreManager { get; set; } = null!;
protected ScoreManager ScoreManager { get; private set; } = null!;
[Resolved]
private RulesetStore rulesets { get; set; } = null!;
protected RulesetStore Rulesets { get; private set; } = null!;
public PlaylistsResultsScreen(ScoreInfo? score, long roomId, PlaylistItem playlistItem)
protected PlaylistItemResultsScreen(ScoreInfo? score, long roomId, PlaylistItem playlistItem)
: base(score)
{
this.roomId = roomId;
this.playlistItem = playlistItem;
RoomId = roomId;
PlaylistItem = playlistItem;
}
[BackgroundDependencyLoader]
@ -74,13 +74,15 @@ namespace osu.Game.Screens.OnlinePlay.Playlists
});
}
protected override APIRequest FetchScores(Action<IEnumerable<ScoreInfo>> scoresCallback)
protected abstract APIRequest<MultiplayerScore> CreateScoreRequest();
protected sealed override APIRequest FetchScores(Action<IEnumerable<ScoreInfo>> scoresCallback)
{
// This performs two requests:
// 1. A request to show the user's score (and scores around).
// 1. A request to show the relevant 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);
var userScoreReq = CreateScoreRequest();
userScoreReq.Success += userScore =>
{
@ -111,11 +113,12 @@ namespace osu.Game.Screens.OnlinePlay.Playlists
setPositions(lowerScores, userScore.Position.Value, 1);
}
performSuccessCallback(scoresCallback, allScores);
Schedule(() => PerformSuccessCallback(scoresCallback, allScores));
hideLoadingSpinners();
};
// On failure, fallback to a normal index.
userScoreReq.Failure += _ => api.Queue(createIndexRequest(scoresCallback));
userScoreReq.Failure += _ => API.Queue(createIndexRequest(scoresCallback));
return userScoreReq;
}
@ -147,8 +150,8 @@ namespace osu.Game.Screens.OnlinePlay.Playlists
private APIRequest createIndexRequest(Action<IEnumerable<ScoreInfo>> scoresCallback, MultiplayerScores? pivot = null)
{
var indexReq = pivot != null
? new IndexPlaylistScoresRequest(roomId, playlistItem.ID, pivot.Cursor, pivot.Params)
: new IndexPlaylistScoresRequest(roomId, playlistItem.ID);
? new IndexPlaylistScoresRequest(RoomId, PlaylistItem.ID, pivot.Cursor, pivot.Params)
: new IndexPlaylistScoresRequest(RoomId, PlaylistItem.ID);
indexReq.Success += r =>
{
@ -163,7 +166,11 @@ namespace osu.Game.Screens.OnlinePlay.Playlists
setPositions(r, pivot, -1);
}
performSuccessCallback(scoresCallback, r.Scores, r);
Schedule(() =>
{
PerformSuccessCallback(scoresCallback, r.Scores, r);
hideLoadingSpinners(pivot);
});
};
indexReq.Failure += _ => hideLoadingSpinners(pivot);
@ -177,26 +184,15 @@ namespace osu.Game.Screens.OnlinePlay.Playlists
/// <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(Action<IEnumerable<ScoreInfo>> callback, List<MultiplayerScore> scores, MultiplayerScores? pivot = null) => Schedule(() =>
protected virtual ScoreInfo[] PerformSuccessCallback(Action<IEnumerable<ScoreInfo>> callback, List<MultiplayerScore> scores, MultiplayerScores? pivot = null)
{
var scoreInfos = scores.Select(s => s.CreateScoreInfo(scoreManager, rulesets, playlistItem, Beatmap.Value.BeatmapInfo)).OrderByTotalScore().ToArray();
var scoreInfos = scores.Select(s => s.CreateScoreInfo(ScoreManager, Rulesets, PlaylistItem, Beatmap.Value.BeatmapInfo)).OrderByTotalScore().ToArray();
// 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.OnlineID == api.LocalUser.Value.Id) ?? scoreInfos.FirstOrDefault();
});
}
// Invoke callback to add the scores.
callback.Invoke(scoreInfos);
// Invoke callback to add the scores. Exclude the user's current score which was added previously.
callback.Invoke(scoreInfos.Where(s => s.OnlineID != Score?.OnlineID));
hideLoadingSpinners(pivot);
});
return scoreInfos;
}
private void hideLoadingSpinners(MultiplayerScores? pivot = null)
{

View File

@ -0,0 +1,37 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using System.Collections.Generic;
using System.Linq;
using osu.Game.Online.API;
using osu.Game.Online.Rooms;
using osu.Game.Scoring;
namespace osu.Game.Screens.OnlinePlay.Playlists
{
/// <summary>
/// Shows a selected arbitrary score for a playlist item, with scores around included.
/// </summary>
public partial class PlaylistItemScoreResultsScreen : PlaylistItemResultsScreen
{
private readonly long scoreId;
public PlaylistItemScoreResultsScreen(long roomId, PlaylistItem playlistItem, long scoreId)
: base(null, roomId, playlistItem)
{
this.scoreId = scoreId;
}
protected override APIRequest<MultiplayerScore> CreateScoreRequest() => new ShowPlaylistScoreRequest(RoomId, PlaylistItem.ID, scoreId);
protected override ScoreInfo[] PerformSuccessCallback(Action<IEnumerable<ScoreInfo>> callback, List<MultiplayerScore> scores, MultiplayerScores? pivot = null)
{
var scoreInfos = base.PerformSuccessCallback(callback, scores, pivot);
Schedule(() => SelectedScore.Value = scoreInfos.SingleOrDefault(score => score.OnlineID == scoreId));
return scoreInfos;
}
}
}

View File

@ -0,0 +1,46 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using System.Collections.Generic;
using System.Linq;
using osu.Game.Online.API;
using osu.Game.Online.Rooms;
using osu.Game.Scoring;
namespace osu.Game.Screens.OnlinePlay.Playlists
{
/// <summary>
/// Shows the user's best score for a given playlist item, with scores around included.
/// </summary>
public partial class PlaylistItemUserResultsScreen : PlaylistItemResultsScreen
{
public PlaylistItemUserResultsScreen(ScoreInfo? score, long roomId, PlaylistItem playlistItem)
: base(score, roomId, playlistItem)
{
}
protected override APIRequest<MultiplayerScore> CreateScoreRequest() => new ShowPlaylistUserScoreRequest(RoomId, PlaylistItem.ID, API.LocalUser.Value.Id);
protected override ScoreInfo[] PerformSuccessCallback(Action<IEnumerable<ScoreInfo>> callback, List<MultiplayerScore> scores, MultiplayerScores? pivot = null)
{
var scoreInfos = scores.Select(s => s.CreateScoreInfo(ScoreManager, Rulesets, PlaylistItem, Beatmap.Value.BeatmapInfo)).OrderByTotalScore().ToArray();
// 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.OnlineID == 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.OnlineID != Score?.OnlineID));
return scoreInfos;
}
}
}

View File

@ -58,7 +58,7 @@ namespace osu.Game.Screens.OnlinePlay.Playlists
protected override ResultsScreen CreateResults(ScoreInfo score)
{
Debug.Assert(Room.RoomID.Value != null);
return new PlaylistsResultsScreen(score, Room.RoomID.Value.Value, PlaylistItem)
return new PlaylistItemUserResultsScreen(score, Room.RoomID.Value.Value, PlaylistItem)
{
AllowRetry = true,
ShowUserStatistics = true,

View File

@ -114,7 +114,7 @@ namespace osu.Game.Screens.OnlinePlay.Playlists
RequestResults = item =>
{
Debug.Assert(RoomId.Value != null);
ParentScreen?.Push(new PlaylistsResultsScreen(null, RoomId.Value.Value, item));
ParentScreen?.Push(new PlaylistItemUserResultsScreen(null, RoomId.Value.Value, item));
}
}
},