2021-03-23 14:00:02 +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.
2021-03-23 15:41:36 +08:00
using System ;
2021-03-23 14:00:02 +08:00
using System.Threading.Tasks ;
2021-03-24 12:17:13 +08:00
using JetBrains.Annotations ;
2021-03-23 14:00:02 +08:00
using osu.Framework.Allocation ;
using osu.Framework.Logging ;
using osu.Framework.Screens ;
using osu.Game.Online.API ;
using osu.Game.Online.Rooms ;
using osu.Game.Scoring ;
namespace osu.Game.Screens.Play
{
2021-03-23 14:33:31 +08:00
/// <summary>
/// A player instance which supports submitting scores to an online store.
/// </summary>
2021-03-23 14:00:02 +08:00
public abstract class SubmittingPlayer : Player
{
2021-03-24 12:02:17 +08:00
/// <summary>
2021-03-24 12:02:37 +08:00
/// The token to be used for the current submission. This is fetched via a request created by <see cref="CreateTokenRequest"/>.
2021-03-24 12:02:17 +08:00
/// </summary>
2021-03-24 12:20:44 +08:00
private long? token ;
2021-03-23 14:00:02 +08:00
[Resolved]
private IAPIProvider api { get ; set ; }
2021-03-23 14:35:06 +08:00
protected SubmittingPlayer ( PlayerConfiguration configuration = null )
2021-03-23 14:00:02 +08:00
: base ( configuration )
{
}
2021-03-23 15:05:40 +08:00
protected override void LoadAsyncComplete ( )
2021-03-25 12:48:41 +08:00
{
if ( ! handleTokenRetrieval ( ) ) return ;
base . LoadAsyncComplete ( ) ;
}
private bool handleTokenRetrieval ( )
2021-03-23 14:00:02 +08:00
{
2021-03-23 15:05:40 +08:00
// Token request construction should happen post-load to allow derived classes to potentially prepare DI backings that are used to create the request.
2021-03-23 15:41:36 +08:00
var tcs = new TaskCompletionSource < bool > ( ) ;
2021-03-23 14:00:02 +08:00
2021-03-23 15:41:36 +08:00
if ( ! api . IsLoggedIn )
2021-03-23 14:00:02 +08:00
{
2021-03-24 12:20:44 +08:00
handleTokenFailure ( new InvalidOperationException ( "API is not online." ) ) ;
2021-03-25 12:48:41 +08:00
return false ;
2021-03-23 15:41:36 +08:00
}
2021-03-23 14:00:02 +08:00
2021-03-24 12:02:37 +08:00
var req = CreateTokenRequest ( ) ;
2021-03-23 14:00:02 +08:00
2021-03-23 15:41:36 +08:00
if ( req = = null )
{
2021-03-24 12:20:44 +08:00
handleTokenFailure ( new InvalidOperationException ( "Request could not be constructed." ) ) ;
2021-03-25 12:48:41 +08:00
return false ;
2021-03-23 15:41:36 +08:00
}
req . Success + = r = >
{
2021-03-24 12:20:44 +08:00
token = r . ID ;
2021-03-23 15:41:36 +08:00
tcs . SetResult ( true ) ;
2021-03-23 14:00:02 +08:00
} ;
2021-03-24 12:20:44 +08:00
req . Failure + = handleTokenFailure ;
2021-03-23 14:00:02 +08:00
api . Queue ( req ) ;
2021-03-23 15:41:36 +08:00
tcs . Task . Wait ( ) ;
2021-03-25 12:48:41 +08:00
return true ;
2021-03-24 12:20:44 +08:00
void handleTokenFailure ( Exception exception )
2021-03-23 15:41:36 +08:00
{
if ( HandleTokenRetrievalFailure ( exception ) )
{
if ( string . IsNullOrEmpty ( exception . Message ) )
Logger . Error ( exception , "Failed to retrieve a score submission token." ) ;
else
Logger . Log ( $"You are not able to submit a score: {exception.Message}" , level : LogLevel . Important ) ;
Schedule ( ( ) = >
{
ValidForResume = false ;
this . Exit ( ) ;
} ) ;
}
tcs . SetResult ( false ) ;
}
2021-03-23 14:00:02 +08:00
}
2021-03-23 15:41:36 +08:00
/// <summary>
/// Called when a token could not be retrieved for submission.
/// </summary>
/// <param name="exception">The error causing the failure.</param>
/// <returns>Whether gameplay should be immediately exited as a result. Returning false allows the gameplay session to continue. Defaults to true.</returns>
protected virtual bool HandleTokenRetrievalFailure ( Exception exception ) = > true ;
2021-03-23 14:45:22 +08:00
protected override async Task PrepareScoreForResultsAsync ( Score score )
2021-03-23 14:00:02 +08:00
{
2021-03-23 14:45:22 +08:00
await base . PrepareScoreForResultsAsync ( score ) . ConfigureAwait ( false ) ;
2021-03-23 14:00:02 +08:00
2021-03-23 15:41:36 +08:00
// token may be null if the request failed but gameplay was still allowed (see HandleTokenRetrievalFailure).
2021-03-24 12:20:44 +08:00
if ( token = = null )
2021-03-23 15:41:36 +08:00
return ;
2021-03-23 14:00:02 +08:00
var tcs = new TaskCompletionSource < bool > ( ) ;
2021-03-24 12:20:44 +08:00
var request = CreateSubmissionRequest ( score , token . Value ) ;
2021-03-23 14:00:02 +08:00
request . Success + = s = >
{
2021-04-13 13:31:41 +08:00
// For the time being, online ID responses are not really useful for anything.
// In addition, the IDs provided via new (lazer) endpoints are based on a different autoincrement from legacy (stable) scores.
//
// Until we better define the server-side logic behind this, let's not store the online ID to avoid potential unique constraint
// conflicts across various systems (ie. solo and multiplayer).
// score.ScoreInfo.OnlineScoreID = s.ID;
2021-03-23 14:00:02 +08:00
tcs . SetResult ( true ) ;
} ;
request . Failure + = e = >
{
Logger . Error ( e , "Failed to submit score" ) ;
tcs . SetResult ( false ) ;
} ;
api . Queue ( request ) ;
await tcs . Task . ConfigureAwait ( false ) ;
}
2021-03-24 12:17:13 +08:00
/// <summary>
/// Construct a request to be used for retrieval of the score token.
/// Can return null, at which point <see cref="HandleTokenRetrievalFailure"/> will be fired.
/// </summary>
[CanBeNull]
2021-03-24 12:02:37 +08:00
protected abstract APIRequest < APIScoreToken > CreateTokenRequest ( ) ;
2021-03-24 12:17:13 +08:00
/// <summary>
/// Construct a request to submit the score.
/// Will only be invoked if the request constructed via <see cref="CreateTokenRequest"/> was successful.
/// </summary>
/// <param name="score">The score to be submitted.</param>
/// <param name="token">The submission token.</param>
protected abstract APIRequest < MultiplayerScore > CreateSubmissionRequest ( Score score , long token ) ;
2021-03-23 14:00:02 +08:00
}
}