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.
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-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
{
2024-06-06 16:42:09 +08:00
public abstract partial class PlaylistItemResultsScreen : ResultsScreen
2020-05-26 17:12:19 +08:00
{
2024-06-06 16:42:09 +08:00
protected readonly long RoomId ;
protected readonly PlaylistItem PlaylistItem ;
2020-05-26 17:12:19 +08:00
2024-04-25 15:52:26 +08:00
protected LoadingSpinner LeftSpinner { get ; private set ; } = null ! ;
protected LoadingSpinner CentreSpinner { get ; private set ; } = null ! ;
protected LoadingSpinner RightSpinner { get ; private set ; } = null ! ;
2020-07-31 20:39:50 +08:00
2024-04-25 15:52:26 +08:00
private MultiplayerScores ? higherScores ;
private MultiplayerScores ? lowerScores ;
2020-07-22 18:40:00 +08:00
[Resolved]
2024-06-06 16:42:09 +08:00
protected IAPIProvider API { get ; private set ; } = null ! ;
2020-06-09 17:53:55 +08:00
2021-09-07 14:18:59 +08:00
[Resolved]
2024-06-06 16:42:09 +08:00
protected ScoreManager ScoreManager { get ; private set ; } = null ! ;
2021-09-07 14:18:59 +08:00
2021-11-15 15:13:03 +08:00
[Resolved]
2024-06-06 16:42:09 +08:00
protected RulesetStore Rulesets { get ; private set ; } = null ! ;
2021-11-15 15:13:03 +08:00
2024-06-06 16:42:09 +08:00
protected PlaylistItemResultsScreen ( ScoreInfo ? score , long roomId , PlaylistItem playlistItem )
2024-02-23 02:15:02 +08:00
: base ( score )
2020-05-26 17:12:19 +08:00
{
2024-06-06 16:42:09 +08:00
RoomId = roomId ;
PlaylistItem = playlistItem ;
2020-05-26 17:12:19 +08:00
}
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
} ) ;
}
2024-06-06 16:42:09 +08:00
protected abstract APIRequest < MultiplayerScore > CreateScoreRequest ( ) ;
protected sealed override APIRequest FetchScores ( Action < IEnumerable < ScoreInfo > > scoresCallback )
2020-05-26 17:12:19 +08:00
{
2020-07-22 18:40:00 +08:00
// This performs two requests:
2024-06-06 16:42:09 +08:00
// 1. A request to show the relevant score (and scores around).
2020-07-28 20:40:11 +08:00
// 2. If that fails, a request to index the room starting from the highest score.
2020-07-22 18:40:00 +08:00
2024-06-06 16:42:09 +08:00
var userScoreReq = CreateScoreRequest ( ) ;
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
}
2024-07-08 19:36:30 +08:00
Schedule ( ( ) = >
{
PerformSuccessCallback ( scoresCallback , allScores ) ;
hideLoadingSpinners ( ) ;
} ) ;
2020-07-22 18:40:00 +08:00
} ;
2020-07-31 16:58:48 +08:00
// On failure, fallback to a normal index.
2024-06-06 16:42:09 +08:00
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
}
2024-04-25 15:52:26 +08:00
protected override APIRequest ? FetchNextPage ( int direction , Action < IEnumerable < ScoreInfo > > scoresCallback )
2020-07-22 19:24:55 +08:00
{
Debug . Assert ( direction = = 1 | | direction = = - 1 ) ;
2024-04-25 15:52:26 +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>
2024-04-25 15:52:26 +08:00
private APIRequest createIndexRequest ( Action < IEnumerable < ScoreInfo > > scoresCallback , MultiplayerScores ? pivot = null )
2020-07-31 16:58:48 +08:00
{
var indexReq = pivot ! = null
2024-06-06 16:42:09 +08:00
? 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
2024-06-06 16:42:09 +08:00
Schedule ( ( ) = >
{
PerformSuccessCallback ( scoresCallback , r . Scores , r ) ;
2024-07-08 19:36:30 +08:00
hideLoadingSpinners ( r ) ;
2024-06-06 16:42:09 +08:00
} ) ;
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>
2024-06-06 16:42:09 +08:00
protected virtual ScoreInfo [ ] PerformSuccessCallback ( Action < IEnumerable < ScoreInfo > > callback , List < MultiplayerScore > scores , MultiplayerScores ? pivot = null )
2020-07-22 19:24:55 +08:00
{
2024-06-06 16:42:09 +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
2024-06-06 16:42:09 +08:00
// Invoke callback to add the scores.
callback . Invoke ( scoreInfos ) ;
2020-07-22 19:24:55 +08:00
2024-06-06 16:42:09 +08:00
return scoreInfos ;
}
2020-07-31 18:57:05 +08:00
2024-04-25 15:52:26 +08:00
private void hideLoadingSpinners ( MultiplayerScores ? pivot = null )
2020-07-31 18:57:05 +08:00
{
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>
2024-04-25 15:52:26 +08:00
private void setPositions ( MultiplayerScores scores , MultiplayerScores ? pivot , int increment )
2020-07-31 21:36:37 +08:00
= > 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>
2024-04-25 15:52:26 +08:00
private void setPositions ( MultiplayerScores scores , int pivotPosition , int increment )
2020-07-31 21:36:37 +08:00
{
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
}
}
}