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.
2020-12-29 15:20:43 +08:00
using System ;
2021-02-01 16:54:56 +08:00
using System.Collections.Generic ;
2020-12-24 09:38:53 +08:00
using System.Diagnostics ;
2020-12-20 23:04:06 +08:00
using System.Linq ;
2020-12-29 15:20:43 +08:00
using JetBrains.Annotations ;
2020-12-20 23:04:06 +08:00
using osu.Framework.Allocation ;
2020-12-23 14:58:50 +08:00
using osu.Framework.Bindables ;
2020-12-20 23:04:06 +08:00
using osu.Framework.Graphics ;
using osu.Framework.Graphics.Containers ;
using osu.Framework.Screens ;
2021-02-10 18:56:59 +08:00
using osu.Framework.Threading ;
using osu.Game.Configuration ;
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 ;
2021-03-03 13:50:54 +08:00
using osu.Game.Overlays ;
using osu.Game.Overlays.Dialog ;
2021-02-01 16:54:56 +08:00
using osu.Game.Rulesets.Mods ;
2020-12-25 23:50:00 +08:00
using osu.Game.Screens.OnlinePlay.Components ;
using osu.Game.Screens.OnlinePlay.Match ;
using osu.Game.Screens.OnlinePlay.Match.Components ;
using osu.Game.Screens.OnlinePlay.Multiplayer.Match ;
using osu.Game.Screens.OnlinePlay.Multiplayer.Participants ;
2021-02-01 12:40:59 +08:00
using osu.Game.Screens.Play.HUD ;
2020-12-20 23:04:06 +08:00
using osu.Game.Users ;
2021-02-01 12:40:59 +08:00
using osuTK ;
2020-12-25 23:50:00 +08:00
using ParticipantsList = osu . Game . Screens . OnlinePlay . Multiplayer . Participants . ParticipantsList ;
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
{
[Cached]
2020-12-25 12:38:11 +08:00
public class MultiplayerMatchSubScreen : RoomSubScreen
2020-12-20 23:04:06 +08:00
{
public override string Title { get ; }
2020-12-24 23:10:29 +08:00
public override string ShortTitle = > "room" ;
2020-12-20 23:04:06 +08:00
[Resolved]
private StatefulMultiplayerClient client { get ; set ; }
2020-12-29 15:20:43 +08:00
[Resolved]
private OngoingOperationTracker ongoingOperationTracker { get ; set ; }
2020-12-29 03:59:38 +08:00
2020-12-25 12:38:11 +08:00
private MultiplayerMatchSettingsOverlay settingsOverlay ;
2020-12-20 23:04:06 +08:00
2021-02-17 20:38:01 +08:00
private readonly IBindable < bool > isConnected = new Bindable < bool > ( ) ;
2020-12-23 14:58:50 +08:00
2020-12-29 15:20:43 +08:00
[CanBeNull]
2020-12-31 01:00:57 +08:00
private IDisposable readyClickOperation ;
2020-12-29 15:20:43 +08:00
2021-01-19 21:52:43 +08:00
private GridContainer mainContent ;
2020-12-25 12:38:11 +08:00
public MultiplayerMatchSubScreen ( Room room )
2020-12-20 23:04:06 +08:00
{
2020-12-24 23:10:29 +08:00
Title = room . RoomID . Value = = null ? "New room" : room . Name . Value ;
2020-12-20 23:04:06 +08:00
Activity . Value = new UserActivity . InLobby ( room ) ;
}
[BackgroundDependencyLoader]
private void load ( )
{
2021-01-17 04:02:30 +08:00
AddRangeInternal ( new Drawable [ ]
2020-12-20 23:04:06 +08:00
{
2021-01-19 21:52:43 +08:00
mainContent = new GridContainer
2020-12-20 23:04:06 +08:00
{
RelativeSizeAxes = Axes . Both ,
Content = new [ ]
{
new Drawable [ ]
{
new Container
{
RelativeSizeAxes = Axes . Both ,
Padding = new MarginPadding
{
2021-02-13 02:23:33 +08:00
Horizontal = HORIZONTAL_OVERFLOW_PADDING + 55 ,
2020-12-20 23:04:06 +08:00
Vertical = 20
} ,
Child = new GridContainer
{
RelativeSizeAxes = Axes . Both ,
RowDimensions = new [ ]
{
new Dimension ( GridSizeMode . AutoSize ) ,
new Dimension ( ) ,
} ,
Content = new [ ]
{
new Drawable [ ]
{
2020-12-25 12:38:11 +08:00
new MultiplayerMatchHeader
2020-12-20 23:04:06 +08:00
{
OpenSettings = ( ) = > settingsOverlay . Show ( )
}
} ,
new Drawable [ ]
{
2021-02-03 13:52:36 +08:00
new Container
2020-12-20 23:04:06 +08:00
{
RelativeSizeAxes = Axes . Both ,
2021-02-03 13:52:36 +08:00
Padding = new MarginPadding { Horizontal = 5 , Vertical = 10 } ,
Child = new GridContainer
2020-12-20 23:04:06 +08:00
{
2021-02-03 13:52:36 +08:00
RelativeSizeAxes = Axes . Both ,
2021-02-05 12:05:11 +08:00
ColumnDimensions = new [ ]
2020-12-20 23:04:06 +08:00
{
2021-02-05 12:05:11 +08:00
new Dimension ( GridSizeMode . Relative , size : 0.5f , maxSize : 400 ) ,
new Dimension ( ) ,
new Dimension ( GridSizeMode . Relative , size : 0.5f , maxSize : 600 ) ,
} ,
2021-02-03 13:52:36 +08:00
Content = new [ ]
2020-12-20 23:04:06 +08:00
{
2021-02-03 13:52:36 +08:00
new Drawable [ ]
2020-12-20 23:04:06 +08:00
{
2021-02-03 13:52:36 +08:00
// Main left column
new GridContainer
2020-12-20 23:04:06 +08:00
{
RelativeSizeAxes = Axes . Both ,
RowDimensions = new [ ]
{
new Dimension ( GridSizeMode . AutoSize )
} ,
Content = new [ ]
{
new Drawable [ ] { new ParticipantsListHeader ( ) } ,
new Drawable [ ]
{
2020-12-23 15:19:03 +08:00
new ParticipantsList
2020-12-20 23:04:06 +08:00
{
RelativeSizeAxes = Axes . Both
} ,
}
}
2021-02-03 13:52:36 +08:00
} ,
2021-02-05 12:05:11 +08:00
// Spacer
null ,
2021-02-03 13:52:36 +08:00
// Main right column
new FillFlowContainer
2020-12-20 23:04:06 +08:00
{
2021-02-03 13:52:36 +08:00
RelativeSizeAxes = Axes . X ,
AutoSizeAxes = Axes . Y ,
Children = new [ ]
2021-02-01 12:40:59 +08:00
{
2021-02-03 13:52:36 +08:00
new FillFlowContainer
2021-02-01 12:40:59 +08:00
{
2021-02-03 13:52:36 +08:00
RelativeSizeAxes = Axes . X ,
AutoSizeAxes = Axes . Y ,
Children = new Drawable [ ]
2021-02-01 12:40:59 +08:00
{
2021-02-03 13:52:36 +08:00
new OverlinedHeader ( "Beatmap" ) ,
new BeatmapSelectionControl { RelativeSizeAxes = Axes . X }
}
} ,
2021-02-16 14:14:48 +08:00
UserModsSection = new FillFlowContainer
2021-02-03 13:52:36 +08:00
{
RelativeSizeAxes = Axes . X ,
AutoSizeAxes = Axes . Y ,
Margin = new MarginPadding { Top = 10 } ,
Children = new Drawable [ ]
2021-02-01 12:40:59 +08:00
{
2021-02-03 13:52:36 +08:00
new OverlinedHeader ( "Extra mods" ) ,
new FillFlowContainer
{
AutoSizeAxes = Axes . Both ,
Direction = FillDirection . Horizontal ,
Spacing = new Vector2 ( 10 , 0 ) ,
Children = new Drawable [ ]
{
new PurpleTriangleButton
{
Anchor = Anchor . CentreLeft ,
Origin = Anchor . CentreLeft ,
Width = 90 ,
Text = "Select" ,
2021-02-16 14:14:48 +08:00
Action = ShowUserModSelect ,
2021-02-03 13:52:36 +08:00
} ,
new ModDisplay
{
Anchor = Anchor . CentreLeft ,
Origin = Anchor . CentreLeft ,
DisplayUnrankedText = false ,
Current = UserMods ,
Scale = new Vector2 ( 0.8f ) ,
} ,
}
}
2021-02-01 12:40:59 +08:00
}
}
}
2020-12-20 23:04:06 +08:00
}
}
}
}
}
} ,
new Drawable [ ]
{
new GridContainer
{
RelativeSizeAxes = Axes . Both ,
RowDimensions = new [ ]
{
new Dimension ( GridSizeMode . AutoSize )
} ,
Content = new [ ]
{
new Drawable [ ] { new OverlinedHeader ( "Chat" ) } ,
new Drawable [ ] { new MatchChatDisplay { RelativeSizeAxes = Axes . Both } }
}
}
}
} ,
}
}
} ,
new Drawable [ ]
{
2020-12-29 14:51:46 +08:00
new MultiplayerMatchFooter
{
2020-12-31 01:00:57 +08:00
OnReadyClick = onReadyClick
2020-12-29 14:51:46 +08:00
}
2020-12-20 23:04:06 +08:00
}
} ,
RowDimensions = new [ ]
{
new Dimension ( ) ,
new Dimension ( GridSizeMode . AutoSize ) ,
}
} ,
2020-12-25 12:38:11 +08:00
settingsOverlay = new MultiplayerMatchSettingsOverlay
2020-12-20 23:04:06 +08:00
{
RelativeSizeAxes = Axes . Both ,
State = { Value = client . Room = = null ? Visibility . Visible : Visibility . Hidden }
}
2021-01-17 04:02:30 +08:00
} ) ;
2021-01-19 05:08:06 +08:00
2021-01-19 21:52:43 +08:00
if ( client . Room = = null )
{
2021-01-19 22:16:39 +08:00
// A new room is being created.
// The main content should be hidden until the settings overlay is hidden, signaling the room is ready to be displayed.
2021-01-19 21:52:43 +08:00
mainContent . Hide ( ) ;
settingsOverlay . State . BindValueChanged ( visibility = >
{
if ( visibility . NewValue = = Visibility . Hidden )
mainContent . Show ( ) ;
} , true ) ;
}
2020-12-20 23:04:06 +08:00
}
protected override void LoadComplete ( )
{
base . LoadComplete ( ) ;
2021-02-19 12:23:01 +08:00
SelectedItem . BindTo ( client . CurrentMatchPlayingItem ) ;
2021-02-16 17:56:13 +08:00
2021-01-18 22:23:51 +08:00
BeatmapAvailability . BindValueChanged ( updateBeatmapAvailability , true ) ;
2021-02-16 13:42:31 +08:00
UserMods . BindValueChanged ( onUserModsChanged ) ;
2020-12-20 23:04:06 +08:00
client . LoadRequested + = onLoadRequested ;
2021-02-11 14:55:08 +08:00
client . RoomUpdated + = onRoomUpdated ;
2020-12-23 14:58:50 +08:00
2021-02-17 20:38:01 +08:00
isConnected . BindTo ( client . IsConnected ) ;
2020-12-23 14:58:50 +08:00
isConnected . BindValueChanged ( connected = >
{
if ( ! connected . NewValue )
Schedule ( this . Exit ) ;
} , true ) ;
2020-12-20 23:04:06 +08:00
}
2021-02-11 15:00:26 +08:00
protected override void UpdateMods ( )
{
if ( SelectedItem . Value = = null | | client . LocalUser = = null )
return ;
// update local mods based on room's reported status for the local user (omitting the base call implementation).
2021-02-17 04:48:19 +08:00
// this makes the server authoritative, and avoids the local user potentially setting mods that the server is not aware of (ie. if the match was started during the selection being changed).
2021-02-11 15:00:26 +08:00
var ruleset = Ruleset . Value . CreateInstance ( ) ;
Mods . Value = client . LocalUser . Mods . Select ( m = > m . ToMod ( ruleset ) ) . Concat ( SelectedItem . Value . RequiredMods ) . ToList ( ) ;
}
2021-03-03 14:24:55 +08:00
[Resolved(canBeNull: true)]
2021-03-03 13:50:54 +08:00
private DialogOverlay dialogOverlay { get ; set ; }
private bool exitConfirmed ;
2020-12-20 23:04:06 +08:00
public override bool OnBackButton ( )
{
2021-03-03 13:50:54 +08:00
if ( client . Room = = null )
{
// room has not been created yet; exit immediately.
return base . OnBackButton ( ) ;
}
if ( settingsOverlay . State . Value = = Visibility . Visible )
2020-12-20 23:04:06 +08:00
{
settingsOverlay . Hide ( ) ;
return true ;
}
2021-03-03 14:24:55 +08:00
if ( ! exitConfirmed & & dialogOverlay ! = null )
2021-03-03 13:50:54 +08:00
{
dialogOverlay . Push ( new ConfirmDialog ( "leave this multiplayer match" , ( ) = >
{
exitConfirmed = true ;
this . Exit ( ) ;
} ) ) ;
return true ;
}
2020-12-20 23:04:06 +08:00
return base . OnBackButton ( ) ;
}
2021-02-10 18:56:59 +08:00
private ModSettingChangeTracker modSettingChangeTracker ;
private ScheduledDelegate debouncedModSettingsUpdate ;
2021-02-01 16:57:32 +08:00
private void onUserModsChanged ( ValueChangedEvent < IReadOnlyList < Mod > > mods )
2021-02-01 16:54:56 +08:00
{
2021-02-10 18:56:59 +08:00
modSettingChangeTracker ? . Dispose ( ) ;
2021-02-01 16:54:56 +08:00
if ( client . Room = = null )
return ;
2021-02-01 18:28:33 +08:00
client . ChangeUserMods ( mods . NewValue ) ;
2021-02-10 18:56:59 +08:00
modSettingChangeTracker = new ModSettingChangeTracker ( mods . NewValue ) ;
modSettingChangeTracker . SettingChanged + = onModSettingsChanged ;
}
private void onModSettingsChanged ( Mod mod )
{
// Debounce changes to mod settings so as to not thrash the network.
debouncedModSettingsUpdate ? . Cancel ( ) ;
2021-02-10 19:16:26 +08:00
debouncedModSettingsUpdate = Scheduler . AddDelayed ( ( ) = >
{
if ( client . Room = = null )
return ;
client . ChangeUserMods ( UserMods . Value ) ;
} , 500 ) ;
2021-02-01 16:54:56 +08:00
}
2020-12-20 23:04:06 +08:00
2021-02-05 15:17:02 +08:00
private void updateBeatmapAvailability ( ValueChangedEvent < BeatmapAvailability > availability )
2021-01-18 15:49:38 +08:00
{
2021-01-18 22:23:51 +08:00
if ( client . Room = = null )
return ;
2021-02-05 15:17:02 +08:00
client . ChangeBeatmapAvailability ( availability . NewValue ) ;
2021-02-05 15:19:45 +08:00
// while this flow is handled server-side, this covers the edge case of the local user being in a ready state and then deleting the current beatmap.
2021-02-05 16:17:29 +08:00
if ( availability . NewValue ! = Online . Rooms . BeatmapAvailability . LocallyAvailable ( )
& & client . LocalUser ? . State = = MultiplayerUserState . Ready )
2021-02-05 15:19:45 +08:00
client . ChangeState ( MultiplayerUserState . Idle ) ;
2021-01-18 15:49:38 +08:00
}
2020-12-31 01:00:57 +08:00
private void onReadyClick ( )
2020-12-29 14:51:46 +08:00
{
2020-12-31 01:00:57 +08:00
Debug . Assert ( readyClickOperation = = null ) ;
readyClickOperation = ongoingOperationTracker . BeginOperation ( ) ;
if ( client . IsHost & & client . LocalUser ? . State = = MultiplayerUserState . Ready )
{
client . StartMatch ( )
. ContinueWith ( t = >
{
// accessing Exception here silences any potential errors from the antecedent task
if ( t . Exception ! = null )
{
// gameplay was not started due to an exception; unblock button.
endOperation ( ) ;
}
// gameplay is starting, the button will be unblocked on load requested.
} ) ;
return ;
}
2020-12-29 16:09:47 +08:00
client . ToggleReady ( )
2021-01-29 15:32:28 +08:00
. ContinueWith ( t = > endOperation ( ) ) ;
2020-12-31 01:00:57 +08:00
void endOperation ( )
{
2021-01-13 07:58:53 +08:00
readyClickOperation ? . Dispose ( ) ;
2020-12-31 01:00:57 +08:00
readyClickOperation = null ;
}
2020-12-29 14:51:46 +08:00
}
2021-02-11 14:55:08 +08:00
private void onRoomUpdated ( )
{
2021-02-11 15:00:26 +08:00
// user mods may have changed.
Scheduler . AddOnce ( UpdateMods ) ;
2021-02-11 14:55:08 +08:00
}
2020-12-24 09:38:53 +08:00
private void onLoadRequested ( )
{
Debug . Assert ( client . Room ! = null ) ;
2020-12-29 13:27:33 +08:00
int [ ] userIds = client . CurrentMatchPlayingUserIds . ToArray ( ) ;
2020-12-24 09:38:53 +08:00
2020-12-25 12:38:11 +08:00
StartPlay ( ( ) = > new MultiplayerPlayer ( SelectedItem . Value , userIds ) ) ;
2020-12-29 15:20:43 +08:00
2021-01-13 07:58:53 +08:00
readyClickOperation ? . Dispose ( ) ;
2020-12-31 01:00:57 +08:00
readyClickOperation = null ;
2020-12-24 09:38:53 +08:00
}
2020-12-20 23:04:06 +08:00
protected override void Dispose ( bool isDisposing )
{
base . Dispose ( isDisposing ) ;
if ( client ! = null )
2021-02-11 14:55:08 +08:00
{
client . RoomUpdated - = onRoomUpdated ;
2020-12-20 23:04:06 +08:00
client . LoadRequested - = onLoadRequested ;
2021-02-11 14:55:08 +08:00
}
2021-02-10 19:09:45 +08:00
modSettingChangeTracker ? . Dispose ( ) ;
2020-12-20 23:04:06 +08:00
}
}
}