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-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 ;
using osu.Game.Online.Multiplayer ;
using osu.Game.Scoring ;
using osu.Game.Screens.Ranking ;
namespace osu.Game.Screens.Multi.Ranking
{
public class TimeshiftResultsScreen : ResultsScreen
{
private readonly int roomId ;
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
2020-05-26 17:12:19 +08:00
public TimeshiftResultsScreen ( ScoreInfo score , int roomId , PlaylistItem playlistItem , bool allowRetry = true )
: base ( score , allowRetry )
{
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 } ;
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>
private void performSuccessCallback ( [ NotNull ] Action < IEnumerable < ScoreInfo > > callback , [ NotNull ] List < MultiplayerScore > scores , [ CanBeNull ] MultiplayerScores pivot = null )
2020-07-22 19:24:55 +08:00
{
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 ( ) ;
} ) ;
2020-07-22 18:40:00 +08:00
}
2020-07-22 19:24:55 +08:00
// Invoke callback to add the scores. Exclude the user's current score which was added previously.
2020-07-31 20:21:48 +08:00
callback . Invoke ( scoreInfos . Where ( s = > s . OnlineScoreID ! = Score ? . OnlineScoreID ) ) ;
2020-07-22 19:24:55 +08:00
2020-07-31 18:57:05 +08:00
hideLoadingSpinners ( pivot ) ;
}
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 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
}
}
}