2020-05-26 17:12:19 +08:00
// 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.
2022-06-17 15:37:17 +08:00
#nullable disable
2020-05-26 17:12:19 +08:00
using System ;
using System.Collections.Generic ;
2020-07-22 19:24:55 +08:00
using System.Diagnostics ;
2020-05-26 17:12:19 +08:00
using System.Linq ;
2020-07-31 16:58:48 +08:00
using JetBrains.Annotations ;
2020-06-09 17:53:55 +08:00
using osu.Framework.Allocation ;
using osu.Framework.Graphics ;
using osu.Framework.Graphics.Containers ;
using osu.Game.Graphics.UserInterface ;
2020-05-26 17:12:19 +08:00
using osu.Game.Online.API ;
2020-12-25 12:38:11 +08:00
using osu.Game.Online.Rooms ;
2021-11-15 15:13:03 +08:00
using osu.Game.Rulesets ;
2020-05-26 17:12:19 +08:00
using osu.Game.Scoring ;
using osu.Game.Screens.Ranking ;
2020-12-25 23:50:00 +08:00
namespace osu.Game.Screens.OnlinePlay.Playlists
2020-05-26 17:12:19 +08:00
{
2020-12-25 12:11:21 +08:00
public partial class PlaylistsResultsScreen : ResultsScreen
2020-05-26 17:12:19 +08:00
{
2021-02-16 18:29:40 +08:00
private readonly long roomId ;
2020-05-26 17:12:19 +08:00
private readonly PlaylistItem playlistItem ;
2020-07-31 20:39:50 +08:00
protected LoadingSpinner LeftSpinner { get ; private set ; }
protected LoadingSpinner CentreSpinner { get ; private set ; }
protected LoadingSpinner RightSpinner { get ; private set ; }
2020-07-28 20:40:11 +08:00
private MultiplayerScores higherScores ;
private MultiplayerScores lowerScores ;
2020-07-22 18:40:00 +08:00
[Resolved]
private IAPIProvider api { get ; set ; }
2020-06-09 17:53:55 +08:00
2021-09-07 14:18:59 +08:00
[Resolved]
private ScoreManager scoreManager { get ; set ; }
2021-11-15 15:13:03 +08:00
[Resolved]
private RulesetStore rulesets { get ; set ; }
2024-02-23 02:15:02 +08:00
public PlaylistsResultsScreen ( [ CanBeNull ] ScoreInfo score , long roomId , PlaylistItem playlistItem )
: base ( score )
2020-05-26 17:12:19 +08:00
{
this . roomId = roomId ;
this . playlistItem = playlistItem ;
}
2020-06-09 17:53:55 +08:00
[BackgroundDependencyLoader]
private void load ( )
{
2020-07-31 18:57:05 +08:00
AddInternal ( new Container
2020-06-09 17:53:55 +08:00
{
2020-07-31 18:57:05 +08:00
RelativeSizeAxes = Axes . Both ,
Padding = new MarginPadding { Bottom = TwoLayerButton . SIZE_EXTENDED . Y } ,
Children = new Drawable [ ]
{
2020-07-31 20:39:50 +08:00
LeftSpinner = new PanelListLoadingSpinner ( ScorePanelList )
2020-07-31 18:57:05 +08:00
{
Anchor = Anchor . CentreLeft ,
Origin = Anchor . Centre ,
} ,
2020-07-31 20:39:50 +08:00
CentreSpinner = new PanelListLoadingSpinner ( ScorePanelList )
2020-07-31 18:57:05 +08:00
{
Anchor = Anchor . Centre ,
Origin = Anchor . Centre ,
State = { Value = Score = = null ? Visibility . Visible : Visibility . Hidden } ,
} ,
2020-07-31 20:39:50 +08:00
RightSpinner = new PanelListLoadingSpinner ( ScorePanelList )
2020-07-31 18:57:05 +08:00
{
Anchor = Anchor . CentreRight ,
Origin = Anchor . Centre ,
} ,
}
2020-06-09 17:53:55 +08:00
} ) ;
}
2020-05-26 17:12:19 +08:00
protected override APIRequest FetchScores ( Action < IEnumerable < ScoreInfo > > scoresCallback )
{
2020-07-22 18:40:00 +08:00
// This performs two requests:
2020-07-28 20:40:11 +08:00
// 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.
2020-07-22 18:40:00 +08:00
var userScoreReq = new ShowPlaylistUserScoreRequest ( roomId , playlistItem . ID , api . LocalUser . Value . Id ) ;
2020-06-09 17:53:55 +08:00
2020-07-22 18:40:00 +08:00
userScoreReq . Success + = userScore = >
2020-06-09 17:53:55 +08:00
{
2020-07-22 18:40:00 +08:00
var allScores = new List < MultiplayerScore > { userScore } ;
2021-12-13 05:54:57 +08:00
// 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 ;
}
2020-07-22 18:40:00 +08:00
if ( userScore . ScoresAround ? . Higher ! = null )
{
allScores . AddRange ( userScore . ScoresAround . Higher . Scores ) ;
2020-07-28 20:40:11 +08:00
higherScores = userScore . ScoresAround . Higher ;
2020-07-31 21:36:37 +08:00
Debug . Assert ( userScore . Position ! = null ) ;
setPositions ( higherScores , userScore . Position . Value , - 1 ) ;
2020-07-22 18:40:00 +08:00
}
if ( userScore . ScoresAround ? . Lower ! = null )
{
allScores . AddRange ( userScore . ScoresAround . Lower . Scores ) ;
2020-07-28 20:40:11 +08:00
lowerScores = userScore . ScoresAround . Lower ;
2020-07-31 21:36:37 +08:00
Debug . Assert ( userScore . Position ! = null ) ;
setPositions ( lowerScores , userScore . Position . Value , 1 ) ;
2020-07-22 18:40:00 +08:00
}
2020-07-22 19:24:55 +08:00
performSuccessCallback ( scoresCallback , allScores ) ;
2020-07-22 18:40:00 +08:00
} ;
2020-07-31 16:58:48 +08:00
// On failure, fallback to a normal index.
userScoreReq . Failure + = _ = > api . Queue ( createIndexRequest ( scoresCallback ) ) ;
2020-06-09 17:53:55 +08:00
2020-07-22 18:40:00 +08:00
return userScoreReq ;
2020-07-22 19:24:55 +08:00
}
protected override APIRequest FetchNextPage ( int direction , Action < IEnumerable < ScoreInfo > > scoresCallback )
{
Debug . Assert ( direction = = 1 | | direction = = - 1 ) ;
2020-07-28 20:40:11 +08:00
MultiplayerScores pivot = direction = = - 1 ? higherScores : lowerScores ;
2020-07-22 18:40:00 +08:00
2020-07-28 20:40:11 +08:00
if ( pivot ? . Cursor = = null )
2020-07-22 19:24:55 +08:00
return null ;
2020-07-31 18:57:05 +08:00
if ( pivot = = higherScores )
2020-07-31 20:39:50 +08:00
LeftSpinner . Show ( ) ;
2020-07-31 18:57:05 +08:00
else
2020-07-31 20:39:50 +08:00
RightSpinner . Show ( ) ;
2020-07-31 18:57:05 +08:00
2020-07-31 16:58:48 +08:00
return createIndexRequest ( scoresCallback , pivot ) ;
}
/// <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 ) ;
2020-07-22 19:24:55 +08:00
indexReq . Success + = r = >
{
2020-07-31 16:58:48 +08:00
if ( pivot = = lowerScores )
2020-07-31 21:36:37 +08:00
{
2020-07-28 20:40:11 +08:00
lowerScores = r ;
2020-07-31 21:36:37 +08:00
setPositions ( r , pivot , 1 ) ;
}
2020-07-31 16:58:48 +08:00
else
2020-07-31 21:36:37 +08:00
{
2020-07-31 16:58:48 +08:00
higherScores = r ;
2020-07-31 21:36:37 +08:00
setPositions ( r , pivot , - 1 ) ;
}
2020-07-22 18:40:00 +08:00
2020-07-31 18:57:05 +08:00
performSuccessCallback ( scoresCallback , r . Scores , r ) ;
2020-07-22 19:24:55 +08:00
} ;
2020-07-31 18:57:05 +08:00
indexReq . Failure + = _ = > hideLoadingSpinners ( pivot ) ;
2020-07-22 18:40:00 +08:00
2020-07-22 19:24:55 +08:00
return indexReq ;
}
2020-07-31 16:58:48 +08:00
/// <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>
2022-08-22 20:31:30 +08:00
private void performSuccessCallback ( [ NotNull ] Action < IEnumerable < ScoreInfo > > callback , [ NotNull ] List < MultiplayerScore > scores , [ CanBeNull ] MultiplayerScores pivot = null ) = > Schedule ( ( ) = >
2020-07-22 19:24:55 +08:00
{
2023-06-16 14:24:30 +08:00
var scoreInfos = scores . Select ( s = > s . CreateScoreInfo ( scoreManager , rulesets , playlistItem , Beatmap . Value . BeatmapInfo ) ) . OrderByTotalScore ( ) . ToArray ( ) ;
2020-07-22 19:24:55 +08:00
2022-08-22 20:31:30 +08:00
// 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 )
2020-07-22 19:24:55 +08:00
{
2022-08-22 20:31:30 +08:00
Schedule ( ( ) = >
2020-07-22 19:24:55 +08:00
{
2022-08-22 20:31:30 +08:00
// 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 ( ) ;
} ) ;
}
2020-07-22 19:24:55 +08:00
2022-08-22 20:31:30 +08:00
// 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 ) ) ;
2020-07-22 19:24:55 +08:00
2022-08-22 20:31:30 +08:00
hideLoadingSpinners ( pivot ) ;
} ) ;
2020-07-31 18:57:05 +08:00
private void hideLoadingSpinners ( [ CanBeNull ] MultiplayerScores pivot = null )
{
2020-07-31 20:39:50 +08:00
CentreSpinner . Hide ( ) ;
2020-07-31 18:57:05 +08:00
if ( pivot = = lowerScores )
2020-07-31 20:39:50 +08:00
RightSpinner . Hide ( ) ;
2020-07-31 18:57:05 +08:00
else if ( pivot = = higherScores )
2020-07-31 20:39:50 +08:00
LeftSpinner . Hide ( ) ;
2020-07-31 18:57:05 +08:00
}
2020-07-31 21:36:37 +08:00
/// <summary>
2020-07-31 22:16:55 +08:00
/// Applies positions to all <see cref="MultiplayerScore"/>s referenced to a given pivot.
2020-07-31 21:36:37 +08:00
/// </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>
2020-07-31 22:16:55 +08:00
/// Applies positions to all <see cref="MultiplayerScore"/>s referenced to a given pivot.
2020-07-31 21:36:37 +08:00
/// </summary>
/// <param name="scores">The <see cref="MultiplayerScores"/> to set positions on.</param>
/// <param name="pivotPosition">The pivot position.</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 , int pivotPosition , int increment )
{
foreach ( var s in scores . Scores )
{
pivotPosition + = increment ;
s . Position = pivotPosition ;
}
}
2020-07-31 18:57:05 +08:00
private partial class PanelListLoadingSpinner : LoadingSpinner
{
private readonly ScorePanelList list ;
/// <summary>
/// Creates a new <see cref="PanelListLoadingSpinner"/>.
/// </summary>
/// <param name="list">The list to track.</param>
/// <param name="withBox">Whether the spinner should have a surrounding black box for visibility.</param>
public PanelListLoadingSpinner ( ScorePanelList list , bool withBox = true )
: base ( withBox )
{
this . list = list ;
}
protected override void Update ( )
{
base . Update ( ) ;
float panelOffset = list . DrawWidth / 2 - ScorePanel . EXPANDED_WIDTH ;
if ( ( Anchor & Anchor . x0 ) > 0 )
X = ( float ) ( panelOffset - list . Current ) ;
else if ( ( Anchor & Anchor . x2 ) > 0 )
X = ( float ) ( list . ScrollableExtent - list . Current - panelOffset ) ;
}
2020-05-26 17:12:19 +08:00
}
}
}