2020-12-20 23:04:06 +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-12-20 23:13:05 +08:00
using System ;
using System.Diagnostics ;
2021-08-09 17:13:28 +08:00
using System.Linq ;
2020-12-20 23:13:05 +08:00
using System.Threading.Tasks ;
using osu.Framework.Allocation ;
2020-12-23 14:58:50 +08:00
using osu.Framework.Bindables ;
2020-12-22 13:59:11 +08:00
using osu.Framework.Logging ;
2022-03-10 19:02:58 +08:00
using osu.Framework.Screens ;
2020-12-24 14:32:55 +08:00
using osu.Game.Graphics.UserInterface ;
2020-12-20 23:04:06 +08:00
using osu.Game.Online.Multiplayer ;
2020-12-25 12:38:11 +08:00
using osu.Game.Online.Rooms ;
2020-12-20 23:13:05 +08:00
using osu.Game.Scoring ;
2020-12-23 16:39:08 +08:00
using osu.Game.Screens.Play ;
2020-12-22 18:09:59 +08:00
using osu.Game.Screens.Play.HUD ;
2020-12-20 23:13:05 +08:00
using osu.Game.Screens.Ranking ;
2021-08-22 09:54:07 +08:00
using osu.Game.Users ;
2020-12-20 23:04:06 +08:00
2020-12-25 23:50:00 +08:00
namespace osu.Game.Screens.OnlinePlay.Multiplayer
2020-12-20 23:04:06 +08:00
{
2021-03-23 14:33:31 +08:00
public partial class MultiplayerPlayer : RoomSubmittingPlayer
2020-12-20 23:04:06 +08:00
{
2020-12-20 23:13:05 +08:00
protected override bool PauseOnFocusLost = > false ;
2021-08-22 09:54:07 +08:00
protected override UserActivity InitialActivity = > new UserActivity . InMultiplayerGame ( Beatmap . Value . BeatmapInfo , Ruleset . Value ) ;
2020-12-20 23:13:05 +08:00
[Resolved]
2021-05-20 14:39:45 +08:00
private MultiplayerClient client { get ; set ; }
2020-12-20 23:13:05 +08:00
2020-12-23 18:57:43 +08:00
private IBindable < bool > isConnected ;
2020-12-20 23:13:05 +08:00
private readonly TaskCompletionSource < bool > resultsReady = new TaskCompletionSource < bool > ( ) ;
2021-08-10 17:39:20 +08:00
private readonly MultiplayerRoomUser [ ] users ;
2020-12-24 09:38:53 +08:00
2020-12-24 14:32:55 +08:00
private LoadingLayer loadingDisplay ;
2022-09-13 15:36:09 +08:00
private MultiplayerGameplayLeaderboard multiplayerLeaderboard ;
2020-12-24 09:38:53 +08:00
/// <summary>
/// Construct a multiplayer player.
/// </summary>
2021-08-24 12:22:06 +08:00
/// <param name="room">The room.</param>
2020-12-24 09:38:53 +08:00
/// <param name="playlistItem">The playlist item to be played.</param>
2021-08-10 17:39:20 +08:00
/// <param name="users">The users which are participating in this game.</param>
2021-08-24 12:22:06 +08:00
public MultiplayerPlayer ( Room room , PlaylistItem playlistItem , MultiplayerRoomUser [ ] users )
: base ( room , playlistItem , new PlayerConfiguration
2020-12-23 16:39:08 +08:00
{
AllowPause = false ,
AllowRestart = false ,
2024-01-04 15:41:52 +08:00
AllowFailAnimation = false ,
2022-08-31 18:50:16 +08:00
AllowSkipping = room . AutoSkip . Value ,
2022-09-13 17:12:49 +08:00
AutomaticallySkipIntro = room . AutoSkip . Value ,
AlwaysShowLeaderboard = true ,
2020-12-23 16:39:08 +08:00
} )
2020-12-20 23:13:05 +08:00
{
2021-08-10 17:39:20 +08:00
this . users = users ;
2020-12-20 23:13:05 +08:00
}
[BackgroundDependencyLoader]
private void load ( )
2020-12-20 23:04:06 +08:00
{
2021-08-05 18:01:07 +08:00
if ( ! LoadedBeatmapSuccessfully )
return ;
2024-01-28 05:34:23 +08:00
ScoreProcessor . ApplyNewJudgementsWhenFailed = true ;
2022-09-13 15:36:09 +08:00
LoadComponentAsync ( new GameplayChatDisplay ( Room )
2021-08-09 17:13:28 +08:00
{
2022-09-13 15:36:09 +08:00
Expanded = { BindTarget = LeaderboardExpandedState } ,
2022-09-13 17:23:47 +08:00
} , chat = > HUDOverlay . LeaderboardFlow . Insert ( 2 , chat ) ) ;
2021-08-09 17:13:28 +08:00
2022-09-13 15:36:09 +08:00
HUDOverlay . Add ( loadingDisplay = new LoadingLayer ( true ) { Depth = float . MaxValue } ) ;
}
2021-08-09 17:13:28 +08:00
2022-09-13 15:36:09 +08:00
protected override GameplayLeaderboard CreateGameplayLeaderboard ( ) = > multiplayerLeaderboard = new MultiplayerGameplayLeaderboard ( users ) ;
2021-08-09 17:13:28 +08:00
2022-09-13 15:36:09 +08:00
protected override void AddLeaderboardToHUD ( GameplayLeaderboard leaderboard )
{
Debug . Assert ( leaderboard = = multiplayerLeaderboard ) ;
2020-12-24 21:32:30 +08:00
2022-09-13 17:23:47 +08:00
HUDOverlay . LeaderboardFlow . Insert ( 0 , leaderboard ) ;
2021-08-17 13:57:48 +08:00
2022-09-13 15:36:09 +08:00
if ( multiplayerLeaderboard . TeamScores . Count > = 2 )
{
LoadComponentAsync ( new GameplayMatchScoreDisplay
{
Team1Score = { BindTarget = multiplayerLeaderboard . TeamScores . First ( ) . Value } ,
Team2Score = { BindTarget = multiplayerLeaderboard . TeamScores . Last ( ) . Value } ,
Expanded = { BindTarget = HUDOverlay . ShowHud } ,
2022-09-13 17:23:47 +08:00
} , scoreDisplay = > HUDOverlay . LeaderboardFlow . Insert ( 1 , scoreDisplay ) ) ;
2022-09-13 15:36:09 +08:00
}
2021-03-23 15:05:40 +08:00
}
protected override void LoadAsyncComplete ( )
{
base . LoadAsyncComplete ( ) ;
2020-12-24 21:32:30 +08:00
2021-08-05 18:01:07 +08:00
if ( ! LoadedBeatmapSuccessfully )
return ;
2021-03-24 12:20:44 +08:00
if ( ! ValidForResume )
return ; // token retrieval may have failed.
2020-12-20 23:13:05 +08:00
2022-04-21 21:37:26 +08:00
client . GameplayStarted + = onGameplayStarted ;
2020-12-20 23:13:05 +08:00
client . ResultsReady + = onResultsReady ;
2022-06-24 20:25:23 +08:00
ScoreProcessor . HasCompleted . BindValueChanged ( _ = >
2020-12-24 14:32:55 +08:00
{
// wait for server to tell us that results are ready (see SubmitScore implementation)
loadingDisplay . Show ( ) ;
} ) ;
2020-12-23 14:58:50 +08:00
isConnected = client . IsConnected . GetBoundCopy ( ) ;
2021-01-12 18:04:16 +08:00
isConnected . BindValueChanged ( connected = > Schedule ( ( ) = >
2020-12-22 13:59:11 +08:00
{
2020-12-23 14:58:50 +08:00
if ( ! connected . NewValue )
2020-12-22 13:59:11 +08:00
{
2020-12-25 12:38:11 +08:00
// messaging to the user about this disconnect will be provided by the MultiplayerMatchSubScreen.
2020-12-23 15:51:11 +08:00
failAndBail ( ) ;
2020-12-23 15:32:58 +08:00
}
2021-01-12 18:04:16 +08:00
} ) , true ) ;
2022-04-08 13:53:14 +08:00
}
protected override void LoadComplete ( )
{
base . LoadComplete ( ) ;
2020-12-23 14:58:50 +08:00
2020-12-22 18:09:59 +08:00
Debug . Assert ( client . Room ! = null ) ;
2020-12-24 14:32:55 +08:00
}
protected override void StartGameplay ( )
{
2022-05-31 18:58:44 +08:00
// We can enter this screen one of two ways:
// 1. Via the automatic natural progression of PlayerLoader into Player.
// We'll arrive here in a Loaded state, and we need to let the server know that we're ready to start.
// 2. Via the server forcefully starting gameplay because players have been hanging out in PlayerLoader for too long.
// We'll arrive here in a Playing state, and we should neither show the loading spinner nor tell the server that we're ready to start (gameplay has already started).
//
// The base call is blocked here because in both cases gameplay is started only when the server says so via onGameplayStarted().
2022-04-21 21:55:13 +08:00
if ( client . LocalUser ? . State = = MultiplayerUserState . Loaded )
{
loadingDisplay . Show ( ) ;
client . ChangeState ( MultiplayerUserState . ReadyForGameplay ) ;
}
2023-10-10 16:58:37 +08:00
// This will pause the clock, pending the gameplay started callback from the server.
GameplayClockContainer . Reset ( ) ;
2020-12-23 15:51:11 +08:00
}
private void failAndBail ( string message = null )
{
if ( ! string . IsNullOrEmpty ( message ) )
Logger . Log ( message , LoggingTarget . Runtime , LogLevel . Important ) ;
2020-12-22 13:59:11 +08:00
2020-12-24 00:37:49 +08:00
Schedule ( ( ) = > PerformExit ( false ) ) ;
2020-12-20 23:13:05 +08:00
}
2022-04-21 21:37:26 +08:00
private void onGameplayStarted ( ) = > Scheduler . Add ( ( ) = >
2020-12-24 14:32:55 +08:00
{
2022-03-10 19:02:58 +08:00
if ( ! this . IsCurrentScreen ( ) )
return ;
2020-12-24 14:32:55 +08:00
loadingDisplay . Hide ( ) ;
base . StartGameplay ( ) ;
} ) ;
2020-12-20 23:13:05 +08:00
2022-03-10 18:52:08 +08:00
private void onResultsReady ( )
2022-03-10 17:50:53 +08:00
{
2022-03-10 18:52:08 +08:00
// Schedule is required to ensure that `TaskCompletionSource.SetResult` is not called more than once.
// A scenario where this can occur is if this instance is not immediately disposed (ie. async disposal queue).
2022-03-10 19:02:58 +08:00
Schedule ( ( ) = >
{
if ( ! this . IsCurrentScreen ( ) )
return ;
resultsReady . SetResult ( true ) ;
} ) ;
2022-03-10 18:52:08 +08:00
}
2020-12-20 23:13:05 +08:00
2021-03-23 14:45:22 +08:00
protected override async Task PrepareScoreForResultsAsync ( Score score )
2020-12-20 23:13:05 +08:00
{
2021-03-23 14:45:22 +08:00
await base . PrepareScoreForResultsAsync ( score ) . ConfigureAwait ( false ) ;
2020-12-20 23:13:05 +08:00
2021-03-08 11:57:16 +08:00
await client . ChangeState ( MultiplayerUserState . FinishedPlay ) . ConfigureAwait ( false ) ;
2020-12-20 23:13:05 +08:00
2020-12-24 18:33:49 +08:00
// Await up to 60 seconds for results to become available (6 api request timeouts).
2020-12-20 23:13:05 +08:00
// This is arbitrary just to not leave the player in an essentially deadlocked state if any connection issues occur.
2021-03-08 11:57:16 +08:00
await Task . WhenAny ( resultsReady . Task , Task . Delay ( TimeSpan . FromSeconds ( 60 ) ) ) . ConfigureAwait ( false ) ;
2020-12-20 23:13:05 +08:00
}
protected override ResultsScreen CreateResults ( ScoreInfo score )
{
2021-08-24 13:25:29 +08:00
Debug . Assert ( Room . RoomID . Value ! = null ) ;
2022-09-13 15:36:09 +08:00
return multiplayerLeaderboard . TeamScores . Count = = 2
? new MultiplayerTeamResultsScreen ( score , Room . RoomID . Value . Value , PlaylistItem , multiplayerLeaderboard . TeamScores )
2021-08-24 13:25:29 +08:00
: new MultiplayerResultsScreen ( score , Room . RoomID . Value . Value , PlaylistItem ) ;
2020-12-20 23:13:05 +08:00
}
protected override void Dispose ( bool isDisposing )
{
base . Dispose ( isDisposing ) ;
if ( client ! = null )
{
2022-04-21 21:37:26 +08:00
client . GameplayStarted - = onGameplayStarted ;
2020-12-20 23:13:05 +08:00
client . ResultsReady - = onResultsReady ;
}
2020-12-20 23:04:06 +08:00
}
}
}