Show selected submission failure messages even in solo

Previously, if a `SubmittingPlayer` instance deemed it okay to proceed
with gameplay despite submission failure, it would silently log all
errors and proceed, but the score would still not be submitted. This
feels a bit anti-user in the cases wherein something is genuinely wrong
with either the client or web, so things like token verification
failures or API failures are now shown as notifications to give the user
an indication that something went wrong at all.

Selected cases (non-user-playable mod, logged out, beatmap is not
online) are still logged silently because those are either known and
expected, or someone is messing with things.
This commit is contained in:
Bartłomiej Dach 2024-02-15 10:40:40 +01:00
parent 95e745c6fb
commit 898d5ce88b
No known key found for this signature in database
3 changed files with 20 additions and 17 deletions

View File

@ -52,7 +52,7 @@ namespace osu.Game.Screens.Play
Scores = { BindTarget = LeaderboardScores } Scores = { BindTarget = LeaderboardScores }
}; };
protected override bool HandleTokenRetrievalFailure(Exception exception) => false; protected override bool ShouldExitOnTokenRetrievalFailure(Exception exception) => false;
protected override Task ImportScore(Score score) protected override Task ImportScore(Score score)
{ {

View File

@ -118,7 +118,7 @@ namespace osu.Game.Screens.Play
token = r.ID; token = r.ID;
tcs.SetResult(true); tcs.SetResult(true);
}; };
req.Failure += handleTokenFailure; req.Failure += ex => handleTokenFailure(ex, displayNotification: true);
api.Queue(req); api.Queue(req);
@ -128,14 +128,20 @@ namespace osu.Game.Screens.Play
return true; return true;
void handleTokenFailure(Exception exception) void handleTokenFailure(Exception exception, bool displayNotification = false)
{ {
tcs.SetResult(false); tcs.SetResult(false);
if (HandleTokenRetrievalFailure(exception)) bool shouldExit = ShouldExitOnTokenRetrievalFailure(exception);
if (displayNotification || shouldExit)
{ {
string whatWillHappen = shouldExit
? "You are not able to submit a score."
: "The following score will not be submitted.";
if (string.IsNullOrEmpty(exception.Message)) if (string.IsNullOrEmpty(exception.Message))
Logger.Error(exception, "Failed to retrieve a score submission token."); Logger.Error(exception, $"{whatWillHappen} Failed to retrieve a score submission token.");
else else
{ {
switch (exception.Message) switch (exception.Message)
@ -143,31 +149,28 @@ namespace osu.Game.Screens.Play
case @"missing token header": case @"missing token header":
case @"invalid client hash": case @"invalid client hash":
case @"invalid verification hash": case @"invalid verification hash":
Logger.Log("You are not able to submit a score. Please ensure that you are using the latest version of the official game releases.", level: LogLevel.Important); Logger.Log($"{whatWillHappen} Please ensure that you are using the latest version of the official game releases.", level: LogLevel.Important);
break; break;
case @"expired token": case @"expired token":
Logger.Log("Score submission failed because your system clock is set incorrectly. Please check your system time, date and timezone.", level: LogLevel.Important); Logger.Log($"{whatWillHappen} Your system clock is set incorrectly. Please check your system time, date and timezone.", level: LogLevel.Important);
break; break;
default: default:
Logger.Log($"You are not able to submit a score: {exception.Message}", level: LogLevel.Important); Logger.Log($"{whatWillHappen} {exception.Message}", level: LogLevel.Important);
break; break;
} }
} }
}
if (shouldExit)
{
Schedule(() => Schedule(() =>
{ {
ValidForResume = false; ValidForResume = false;
this.Exit(); this.Exit();
}); });
} }
else
{
// Gameplay is allowed to continue, but we still should keep track of the error.
// In the future, this should be visible to the user in some way.
Logger.Log($"Score submission token retrieval failed ({exception.Message})");
}
} }
} }
@ -176,7 +179,7 @@ namespace osu.Game.Screens.Play
/// </summary> /// </summary>
/// <param name="exception">The error causing the failure.</param> /// <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> /// <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; protected virtual bool ShouldExitOnTokenRetrievalFailure(Exception exception) => true;
protected override async Task PrepareScoreForResultsAsync(Score score) protected override async Task PrepareScoreForResultsAsync(Score score)
{ {
@ -237,7 +240,7 @@ namespace osu.Game.Screens.Play
/// <summary> /// <summary>
/// Construct a request to be used for retrieval of the score token. /// Construct a request to be used for retrieval of the score token.
/// Can return null, at which point <see cref="HandleTokenRetrievalFailure"/> will be fired. /// Can return null, at which point <see cref="ShouldExitOnTokenRetrievalFailure"/> will be fired.
/// </summary> /// </summary>
[CanBeNull] [CanBeNull]
protected abstract APIRequest<APIScoreToken> CreateTokenRequest(); protected abstract APIRequest<APIScoreToken> CreateTokenRequest();

View File

@ -61,7 +61,7 @@ namespace osu.Game.Tests.Visual
PauseOnFocusLost = pauseOnFocusLost; PauseOnFocusLost = pauseOnFocusLost;
} }
protected override bool HandleTokenRetrievalFailure(Exception exception) => false; protected override bool ShouldExitOnTokenRetrievalFailure(Exception exception) => false;
protected override APIRequest<APIScoreToken> CreateTokenRequest() protected override APIRequest<APIScoreToken> CreateTokenRequest()
{ {