2021-04-08 21:14:30 +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-05-12 22:29:28 +08:00
using System ;
2021-04-08 21:14:30 +08:00
using System.Collections.Generic ;
using System.Linq ;
using NUnit.Framework ;
using osu.Framework.Allocation ;
2022-01-03 16:31:12 +08:00
using osu.Framework.Extensions ;
2021-08-25 16:30:37 +08:00
using osu.Framework.Extensions.ObjectExtensions ;
2021-04-08 21:14:30 +08:00
using osu.Framework.Graphics ;
2021-08-13 11:31:39 +08:00
using osu.Framework.Graphics.Containers ;
2021-04-08 21:14:30 +08:00
using osu.Framework.Testing ;
using osu.Game.Beatmaps ;
2021-08-16 12:21:37 +08:00
using osu.Game.Configuration ;
2022-09-07 19:00:24 +08:00
using osu.Game.Online.API ;
2021-11-04 17:02:44 +08:00
using osu.Game.Online.API.Requests.Responses ;
2021-08-10 17:39:20 +08:00
using osu.Game.Online.Multiplayer ;
2021-08-09 18:05:23 +08:00
using osu.Game.Online.Multiplayer.MatchTypes.TeamVersus ;
2022-09-07 19:00:24 +08:00
using osu.Game.Rulesets.Osu.Mods ;
2021-06-11 17:13:54 +08:00
using osu.Game.Rulesets.UI ;
2021-04-08 21:14:30 +08:00
using osu.Game.Screens.OnlinePlay.Multiplayer.Spectate ;
using osu.Game.Screens.Play ;
2021-08-13 11:31:39 +08:00
using osu.Game.Screens.Play.HUD ;
using osu.Game.Screens.Play.PlayerSettings ;
2022-05-12 22:29:28 +08:00
using osu.Game.Storyboards ;
2021-04-08 21:14:30 +08:00
using osu.Game.Tests.Beatmaps.IO ;
2022-05-12 22:29:28 +08:00
using osuTK ;
2021-08-25 16:30:37 +08:00
using osuTK.Graphics ;
2021-04-08 21:14:30 +08:00
namespace osu.Game.Tests.Visual.Multiplayer
{
2022-11-24 13:32:20 +08:00
public partial class TestSceneMultiSpectatorScreen : MultiplayerTestScene
2021-04-08 21:14:30 +08:00
{
[Resolved]
2022-07-07 16:51:49 +08:00
private OsuGameBase game { get ; set ; } = null ! ;
2021-04-08 21:14:30 +08:00
2021-08-16 12:21:37 +08:00
[Resolved]
2022-07-07 16:51:49 +08:00
private OsuConfigManager config { get ; set ; } = null ! ;
2021-08-16 12:21:37 +08:00
2021-04-08 21:14:30 +08:00
[Resolved]
2022-07-07 16:51:49 +08:00
private BeatmapManager beatmapManager { get ; set ; } = null ! ;
2021-04-08 21:14:30 +08:00
2022-07-07 16:51:49 +08:00
private MultiSpectatorScreen spectatorScreen = null ! ;
2021-04-08 21:14:30 +08:00
2021-08-10 17:39:20 +08:00
private readonly List < MultiplayerRoomUser > playingUsers = new List < MultiplayerRoomUser > ( ) ;
2021-04-08 21:14:30 +08:00
2022-07-07 16:51:49 +08:00
private BeatmapSetInfo importedSet = null ! ;
private BeatmapInfo importedBeatmap = null ! ;
2021-04-08 21:14:30 +08:00
private int importedBeatmapId ;
[BackgroundDependencyLoader]
private void load ( )
{
2021-12-17 17:26:12 +08:00
importedSet = BeatmapImportHelper . LoadOszIntoOsu ( game , virtualTrack : true ) . GetResultSafely ( ) ;
2022-01-27 14:19:48 +08:00
importedBeatmap = importedSet . Beatmaps . First ( b = > b . Ruleset . OnlineID = = 0 ) ;
2021-11-22 13:55:41 +08:00
importedBeatmapId = importedBeatmap . OnlineID ;
2021-04-08 21:14:30 +08:00
}
2022-07-29 14:27:39 +08:00
public override void SetUpSteps ( )
{
base . SetUpSteps ( ) ;
AddStep ( "clear playing users" , ( ) = > playingUsers . Clear ( ) ) ;
}
2021-04-08 21:14:30 +08:00
2021-04-16 11:25:29 +08:00
[Test]
public void TestDelayedStart ( )
{
AddStep ( "start players silently" , ( ) = >
{
2022-02-16 08:43:28 +08:00
OnlinePlayDependencies . MultiplayerClient . AddUser ( new APIUser { Id = PLAYER_1_ID } , true ) ;
OnlinePlayDependencies . MultiplayerClient . AddUser ( new APIUser { Id = PLAYER_2_ID } , true ) ;
2021-08-09 18:05:23 +08:00
2021-08-10 17:39:20 +08:00
playingUsers . Add ( new MultiplayerRoomUser ( PLAYER_1_ID ) ) ;
playingUsers . Add ( new MultiplayerRoomUser ( PLAYER_2_ID ) ) ;
2021-04-16 11:25:29 +08:00
} ) ;
loadSpectateScreen ( false ) ;
AddWaitStep ( "wait a bit" , 10 ) ;
2022-02-22 16:56:07 +08:00
AddStep ( "load player first_player_id" , ( ) = > SpectatorClient . SendStartPlay ( PLAYER_1_ID , importedBeatmapId ) ) ;
2021-04-22 22:39:02 +08:00
AddUntilStep ( "one player added" , ( ) = > spectatorScreen . ChildrenOfType < Player > ( ) . Count ( ) = = 1 ) ;
2021-04-16 11:25:29 +08:00
AddWaitStep ( "wait a bit" , 10 ) ;
2022-02-22 16:56:07 +08:00
AddStep ( "load player second_player_id" , ( ) = > SpectatorClient . SendStartPlay ( PLAYER_2_ID , importedBeatmapId ) ) ;
2021-04-22 22:39:02 +08:00
AddUntilStep ( "two players added" , ( ) = > spectatorScreen . ChildrenOfType < Player > ( ) . Count ( ) = = 2 ) ;
2021-04-16 11:25:29 +08:00
}
2021-04-08 21:14:30 +08:00
[Test]
public void TestGeneral ( )
{
2021-08-25 23:25:31 +08:00
int [ ] userIds = getPlayerIds ( 4 ) ;
2021-04-08 21:14:30 +08:00
start ( userIds ) ;
loadSpectateScreen ( ) ;
sendFrames ( userIds , 1000 ) ;
AddWaitStep ( "wait a bit" , 20 ) ;
}
2021-08-13 11:31:39 +08:00
[Test]
2021-08-13 17:12:20 +08:00
public void TestSpectatorPlayerInteractiveElementsHidden ( )
2021-08-13 11:31:39 +08:00
{
2021-08-16 12:21:37 +08:00
HUDVisibilityMode originalConfigValue = default ;
AddStep ( "get original config hud visibility" , ( ) = > originalConfigValue = config . Get < HUDVisibilityMode > ( OsuSetting . HUDVisibilityMode ) ) ;
AddStep ( "set config hud visibility to always" , ( ) = > config . SetValue ( OsuSetting . HUDVisibilityMode , HUDVisibilityMode . Always ) ) ;
2021-08-13 11:31:39 +08:00
start ( new [ ] { PLAYER_1_ID , PLAYER_2_ID } ) ;
loadSpectateScreen ( false ) ;
AddUntilStep ( "wait for player loaders" , ( ) = > this . ChildrenOfType < PlayerLoader > ( ) . Count ( ) = = 2 ) ;
2021-08-13 12:30:24 +08:00
AddAssert ( "all player loader settings hidden" , ( ) = > this . ChildrenOfType < PlayerLoader > ( ) . All ( l = > ! l . ChildrenOfType < FillFlowContainer < PlayerSettingsGroup > > ( ) . Any ( ) ) ) ;
2021-08-13 11:31:39 +08:00
AddUntilStep ( "wait for players to load" , ( ) = > spectatorScreen . AllPlayersLoaded ) ;
2021-08-13 18:01:17 +08:00
// components wrapped in skinnable target containers load asynchronously, potentially taking more than one frame to load.
2021-08-13 20:06:11 +08:00
// therefore use until step rather than direct assert to account for that.
AddUntilStep ( "all interactive elements removed" , ( ) = > this . ChildrenOfType < Player > ( ) . All ( p = >
2021-08-13 12:30:24 +08:00
! p . ChildrenOfType < PlayerSettingsOverlay > ( ) . Any ( ) & &
! p . ChildrenOfType < HoldForMenuButton > ( ) . Any ( ) & &
2023-01-18 14:11:13 +08:00
p . ChildrenOfType < ArgonSongProgressBar > ( ) . SingleOrDefault ( ) ? . Interactive = = false ) ) ;
2021-08-16 12:21:37 +08:00
AddStep ( "restore config hud visibility" , ( ) = > config . SetValue ( OsuSetting . HUDVisibilityMode , originalConfigValue ) ) ;
2021-08-13 11:31:39 +08:00
}
2021-08-09 18:05:23 +08:00
[Test]
public void TestTeamDisplay ( )
{
AddStep ( "start players" , ( ) = >
{
2022-02-16 08:43:28 +08:00
var player1 = OnlinePlayDependencies . MultiplayerClient . AddUser ( new APIUser { Id = PLAYER_1_ID } , true ) ;
2021-08-09 18:05:23 +08:00
player1 . MatchState = new TeamVersusUserState
{
TeamID = 0 ,
} ;
2022-02-16 08:43:28 +08:00
var player2 = OnlinePlayDependencies . MultiplayerClient . AddUser ( new APIUser { Id = PLAYER_2_ID } , true ) ;
2021-08-09 18:05:23 +08:00
player2 . MatchState = new TeamVersusUserState
{
TeamID = 1 ,
} ;
2022-02-22 16:56:07 +08:00
SpectatorClient . SendStartPlay ( player1 . UserID , importedBeatmapId ) ;
SpectatorClient . SendStartPlay ( player2 . UserID , importedBeatmapId ) ;
2021-08-09 18:05:23 +08:00
2021-08-12 09:38:20 +08:00
playingUsers . Add ( player1 ) ;
playingUsers . Add ( player2 ) ;
2021-08-09 18:05:23 +08:00
} ) ;
loadSpectateScreen ( ) ;
sendFrames ( PLAYER_1_ID , 1000 ) ;
sendFrames ( PLAYER_2_ID , 1000 ) ;
AddWaitStep ( "wait a bit" , 20 ) ;
}
2021-06-11 18:15:53 +08:00
[Test]
public void TestTimeDoesNotProgressWhileAllPlayersPaused ( )
{
start ( new [ ] { PLAYER_1_ID , PLAYER_2_ID } ) ;
loadSpectateScreen ( ) ;
2021-06-29 21:45:18 +08:00
sendFrames ( PLAYER_1_ID , 40 ) ;
sendFrames ( PLAYER_2_ID , 20 ) ;
2021-06-11 18:15:53 +08:00
2022-08-26 15:56:03 +08:00
waitUntilPaused ( PLAYER_2_ID ) ;
checkRunningInstant ( PLAYER_1_ID ) ;
2021-06-11 18:15:53 +08:00
AddAssert ( "master clock still running" , ( ) = > this . ChildrenOfType < MasterGameplayClockContainer > ( ) . Single ( ) . IsRunning ) ;
2022-08-26 15:56:03 +08:00
waitUntilPaused ( PLAYER_1_ID ) ;
2021-06-11 18:15:53 +08:00
AddUntilStep ( "master clock paused" , ( ) = > ! this . ChildrenOfType < MasterGameplayClockContainer > ( ) . Single ( ) . IsRunning ) ;
}
2021-04-08 21:14:30 +08:00
[Test]
public void TestPlayersMustStartSimultaneously ( )
{
2021-04-26 16:22:16 +08:00
start ( new [ ] { PLAYER_1_ID , PLAYER_2_ID } ) ;
2021-04-08 21:14:30 +08:00
loadSpectateScreen ( ) ;
// Send frames for one player only, both should remain paused.
2021-04-26 16:22:16 +08:00
sendFrames ( PLAYER_1_ID , 20 ) ;
2022-08-26 15:56:03 +08:00
checkPausedInstant ( PLAYER_1_ID ) ;
checkPausedInstant ( PLAYER_2_ID ) ;
2021-04-08 21:14:30 +08:00
// Send frames for the other player, both should now start playing.
2021-04-26 16:22:16 +08:00
sendFrames ( PLAYER_2_ID , 20 ) ;
2022-08-26 15:56:03 +08:00
checkRunningInstant ( PLAYER_1_ID ) ;
checkRunningInstant ( PLAYER_2_ID ) ;
2021-04-08 21:14:30 +08:00
}
[Test]
2021-04-26 15:45:20 +08:00
public void TestPlayersDoNotStartSimultaneouslyIfBufferingForMaximumStartDelay ( )
2021-04-08 21:14:30 +08:00
{
2021-04-26 16:22:16 +08:00
start ( new [ ] { PLAYER_1_ID , PLAYER_2_ID } ) ;
2021-04-08 21:14:30 +08:00
loadSpectateScreen ( ) ;
// Send frames for one player only, both should remain paused.
2021-04-26 16:22:16 +08:00
sendFrames ( PLAYER_1_ID , 1000 ) ;
2022-08-26 15:56:03 +08:00
checkPausedInstant ( PLAYER_1_ID ) ;
checkPausedInstant ( PLAYER_2_ID ) ;
2021-04-08 21:14:30 +08:00
2021-04-26 15:45:20 +08:00
// Wait for the start delay seconds...
2022-08-24 14:07:04 +08:00
AddWaitStep ( "wait maximum start delay seconds" , ( int ) ( SpectatorSyncManager . MAXIMUM_START_DELAY / TimePerAction ) ) ;
2021-04-08 21:14:30 +08:00
// Player 1 should start playing by itself, player 2 should remain paused.
2022-08-26 15:56:03 +08:00
checkRunningInstant ( PLAYER_1_ID ) ;
checkPausedInstant ( PLAYER_2_ID ) ;
2021-04-08 21:14:30 +08:00
}
[Test]
public void TestPlayersContinueWhileOthersBuffer ( )
{
2021-04-26 16:22:16 +08:00
start ( new [ ] { PLAYER_1_ID , PLAYER_2_ID } ) ;
2021-04-08 21:14:30 +08:00
loadSpectateScreen ( ) ;
// Send initial frames for both players. A few more for player 1.
2021-04-26 16:22:16 +08:00
sendFrames ( PLAYER_1_ID , 20 ) ;
2021-07-05 23:52:39 +08:00
sendFrames ( PLAYER_2_ID ) ;
2022-08-26 15:56:03 +08:00
checkRunningInstant ( PLAYER_1_ID ) ;
checkRunningInstant ( PLAYER_2_ID ) ;
2021-04-08 21:14:30 +08:00
// Eventually player 2 will pause, player 1 must remain running.
2022-08-26 15:56:03 +08:00
waitUntilPaused ( PLAYER_2_ID ) ;
checkRunningInstant ( PLAYER_1_ID ) ;
2021-04-08 21:14:30 +08:00
// Eventually both players will run out of frames and should pause.
2022-08-26 15:56:03 +08:00
waitUntilPaused ( PLAYER_1_ID ) ;
checkPausedInstant ( PLAYER_2_ID ) ;
2021-04-08 21:14:30 +08:00
// Send more frames for the first player only. Player 1 should start playing with player 2 remaining paused.
2021-04-26 16:22:16 +08:00
sendFrames ( PLAYER_1_ID , 20 ) ;
2022-08-26 15:56:03 +08:00
checkPausedInstant ( PLAYER_2_ID ) ;
checkRunningInstant ( PLAYER_1_ID ) ;
2021-04-08 21:14:30 +08:00
// Send more frames for the second player. Both should be playing
2021-04-26 16:22:16 +08:00
sendFrames ( PLAYER_2_ID , 20 ) ;
2022-08-26 15:56:03 +08:00
checkRunningInstant ( PLAYER_2_ID ) ;
checkRunningInstant ( PLAYER_1_ID ) ;
2021-04-08 21:14:30 +08:00
}
[Test]
public void TestPlayersCatchUpAfterFallingBehind ( )
{
2021-04-26 16:22:16 +08:00
start ( new [ ] { PLAYER_1_ID , PLAYER_2_ID } ) ;
2021-04-08 21:14:30 +08:00
loadSpectateScreen ( ) ;
// Send initial frames for both players. A few more for player 1.
2021-04-26 16:22:16 +08:00
sendFrames ( PLAYER_1_ID , 1000 ) ;
2021-06-30 19:16:57 +08:00
sendFrames ( PLAYER_2_ID , 30 ) ;
2022-08-26 15:56:03 +08:00
checkRunningInstant ( PLAYER_1_ID ) ;
checkRunningInstant ( PLAYER_2_ID ) ;
2021-04-08 21:14:30 +08:00
// Eventually player 2 will run out of frames and should pause.
2022-08-26 15:56:03 +08:00
waitUntilPaused ( PLAYER_2_ID ) ;
2021-04-08 21:14:30 +08:00
AddWaitStep ( "wait a few more frames" , 10 ) ;
// Send more frames for player 2. It should unpause.
2021-04-26 16:22:16 +08:00
sendFrames ( PLAYER_2_ID , 1000 ) ;
2022-08-26 15:56:03 +08:00
checkRunningInstant ( PLAYER_2_ID ) ;
2021-04-08 21:14:30 +08:00
// Player 2 should catch up to player 1 after unpausing.
2021-04-26 16:22:16 +08:00
waitForCatchup ( PLAYER_2_ID ) ;
2021-04-13 21:40:10 +08:00
AddWaitStep ( "wait a bit" , 10 ) ;
2021-04-08 21:14:30 +08:00
}
2021-04-22 21:59:47 +08:00
[Test]
public void TestMostInSyncUserIsAudioSource ( )
{
2021-04-26 16:22:16 +08:00
start ( new [ ] { PLAYER_1_ID , PLAYER_2_ID } ) ;
2021-04-22 21:59:47 +08:00
loadSpectateScreen ( ) ;
2022-08-25 15:44:12 +08:00
// With no frames, the synchronisation state will be TooFarAhead.
// In this state, all players should be muted.
2021-04-26 18:01:30 +08:00
assertMuted ( PLAYER_1_ID , true ) ;
assertMuted ( PLAYER_2_ID , true ) ;
2021-04-22 21:59:47 +08:00
2022-08-25 15:44:12 +08:00
// Send frames for both players, with more frames for player 2.
sendFrames ( PLAYER_1_ID , 5 ) ;
2022-08-25 16:30:13 +08:00
sendFrames ( PLAYER_2_ID , 20 ) ;
2022-08-25 15:44:12 +08:00
// While both players are running, one of them should be un-muted.
2022-08-26 15:56:03 +08:00
waitUntilRunning ( PLAYER_1_ID ) ;
2022-08-25 15:44:12 +08:00
assertOnePlayerNotMuted ( ) ;
2021-04-22 21:59:47 +08:00
2022-08-25 15:44:12 +08:00
// After player 1 runs out of frames, the un-muted player should always be player 2.
2022-08-26 15:56:03 +08:00
waitUntilPaused ( PLAYER_1_ID ) ;
waitUntilRunning ( PLAYER_2_ID ) ;
2021-04-26 18:01:30 +08:00
assertMuted ( PLAYER_1_ID , true ) ;
assertMuted ( PLAYER_2_ID , false ) ;
2021-04-22 21:59:47 +08:00
2021-04-26 16:22:16 +08:00
sendFrames ( PLAYER_1_ID , 100 ) ;
waitForCatchup ( PLAYER_1_ID ) ;
2022-08-26 15:56:03 +08:00
waitUntilPaused ( PLAYER_2_ID ) ;
2021-04-26 18:01:30 +08:00
assertMuted ( PLAYER_1_ID , false ) ;
assertMuted ( PLAYER_2_ID , true ) ;
2021-04-22 21:59:47 +08:00
2021-04-26 16:22:16 +08:00
sendFrames ( PLAYER_2_ID , 100 ) ;
waitForCatchup ( PLAYER_2_ID ) ;
2021-04-26 18:01:30 +08:00
assertMuted ( PLAYER_1_ID , false ) ;
assertMuted ( PLAYER_2_ID , true ) ;
2021-04-22 21:59:47 +08:00
}
2021-06-11 17:13:54 +08:00
[Test]
public void TestSpectatingDuringGameplay ( )
{
2021-10-27 12:09:30 +08:00
int [ ] players = { PLAYER_1_ID , PLAYER_2_ID } ;
2021-06-11 17:13:54 +08:00
start ( players ) ;
sendFrames ( players , 300 ) ;
loadSpectateScreen ( ) ;
sendFrames ( players , 300 ) ;
AddUntilStep ( "playing from correct point in time" , ( ) = > this . ChildrenOfType < DrawableRuleset > ( ) . All ( r = > r . FrameStableClock . CurrentTime > 30000 ) ) ;
}
[Test]
public void TestSpectatingDuringGameplayWithLateFrames ( )
{
start ( new [ ] { PLAYER_1_ID , PLAYER_2_ID } ) ;
sendFrames ( new [ ] { PLAYER_1_ID , PLAYER_2_ID } , 300 ) ;
loadSpectateScreen ( ) ;
sendFrames ( PLAYER_1_ID , 300 ) ;
2022-08-24 14:07:04 +08:00
AddWaitStep ( "wait maximum start delay seconds" , ( int ) ( SpectatorSyncManager . MAXIMUM_START_DELAY / TimePerAction ) ) ;
2022-08-26 15:56:03 +08:00
waitUntilRunning ( PLAYER_1_ID ) ;
2021-06-11 17:13:54 +08:00
sendFrames ( PLAYER_2_ID , 300 ) ;
AddUntilStep ( "player 2 playing from correct point in time" , ( ) = > getPlayer ( PLAYER_2_ID ) . ChildrenOfType < DrawableRuleset > ( ) . Single ( ) . FrameStableClock . CurrentTime > 30000 ) ;
}
2022-09-07 19:00:24 +08:00
[Test]
public void TestGameplayRateAdjust ( )
{
start ( getPlayerIds ( 4 ) , mods : new [ ] { new APIMod ( new OsuModDoubleTime ( ) ) } ) ;
loadSpectateScreen ( ) ;
sendFrames ( getPlayerIds ( 4 ) , 300 ) ;
AddUntilStep ( "wait for correct track speed" , ( ) = > Beatmap . Value . Track . Rate , ( ) = > Is . EqualTo ( 1.5 ) ) ;
}
2021-08-25 16:30:37 +08:00
[Test]
public void TestPlayersLeaveWhileSpectating ( )
{
2021-08-27 18:14:56 +08:00
start ( getPlayerIds ( 4 ) ) ;
sendFrames ( getPlayerIds ( 4 ) , 300 ) ;
2021-08-25 16:30:37 +08:00
loadSpectateScreen ( ) ;
2021-08-27 18:14:56 +08:00
for ( int count = 3 ; count > = 0 ; count - - )
2021-08-25 16:30:37 +08:00
{
2021-10-27 12:04:41 +08:00
int id = PLAYER_1_ID + count ;
2021-08-25 16:30:37 +08:00
2021-08-25 23:28:23 +08:00
end ( id ) ;
2021-08-27 18:14:56 +08:00
AddUntilStep ( $"{id} area grayed" , ( ) = > getInstance ( id ) . Colour ! = Color4 . White ) ;
AddUntilStep ( $"{id} score quit set" , ( ) = > getLeaderboardScore ( id ) . HasQuit . Value ) ;
2021-08-25 23:25:31 +08:00
sendFrames ( getPlayerIds ( count ) , 300 ) ;
2021-08-25 16:30:37 +08:00
}
2021-08-27 18:15:12 +08:00
2022-07-07 16:51:49 +08:00
Player ? player = null ;
2021-08-27 18:15:12 +08:00
AddStep ( $"get {PLAYER_1_ID} player instance" , ( ) = > player = getInstance ( PLAYER_1_ID ) . ChildrenOfType < Player > ( ) . Single ( ) ) ;
start ( new [ ] { PLAYER_1_ID } ) ;
sendFrames ( PLAYER_1_ID , 300 ) ;
AddAssert ( $"{PLAYER_1_ID} player instance still same" , ( ) = > getInstance ( PLAYER_1_ID ) . ChildrenOfType < Player > ( ) . Single ( ) = = player ) ;
AddAssert ( $"{PLAYER_1_ID} area still grayed" , ( ) = > getInstance ( PLAYER_1_ID ) . Colour ! = Color4 . White ) ;
AddAssert ( $"{PLAYER_1_ID} score quit still set" , ( ) = > getLeaderboardScore ( PLAYER_1_ID ) . HasQuit . Value ) ;
2021-08-25 16:30:37 +08:00
}
2021-08-11 17:16:25 +08:00
/// <summary>
2022-05-12 22:29:28 +08:00
/// Tests spectating with a beatmap that has a high <see cref="BeatmapInfo.AudioLeadIn"/> value.
2022-08-24 15:33:07 +08:00
///
/// This test is not intended not to check the correct initial time value, but only to guard against
/// gameplay potentially getting stuck in a stopped state due to lead in time being present.
2021-08-11 17:16:25 +08:00
/// </summary>
[Test]
2022-05-12 22:29:28 +08:00
public void TestAudioLeadIn ( ) = > testLeadIn ( b = > b . BeatmapInfo . AudioLeadIn = 2000 ) ;
/// <summary>
/// Tests spectating with a beatmap that has a storyboard element with a negative start time (i.e. intro storyboard element).
2022-08-24 15:33:07 +08:00
///
/// This test is not intended not to check the correct initial time value, but only to guard against
/// gameplay potentially getting stuck in a stopped state due to lead in time being present.
2022-05-12 22:29:28 +08:00
/// </summary>
[Test]
public void TestIntroStoryboardElement ( ) = > testLeadIn ( b = >
{
var sprite = new StoryboardSprite ( "unknown" , Anchor . TopLeft , Vector2 . Zero ) ;
sprite . TimelineGroup . Alpha . Add ( Easing . None , - 2000 , 0 , 0 , 1 ) ;
b . Storyboard . GetLayer ( "Background" ) . Add ( sprite ) ;
} ) ;
2022-07-07 16:51:49 +08:00
private void testLeadIn ( Action < WorkingBeatmap > ? applyToBeatmap = null )
2021-08-11 17:16:25 +08:00
{
start ( PLAYER_1_ID ) ;
2022-05-12 22:29:28 +08:00
loadSpectateScreen ( false , applyToBeatmap ) ;
2021-08-11 17:16:25 +08:00
// to ensure negative gameplay start time does not affect spectator, send frames exactly after StartGameplay().
// (similar to real spectating sessions in which the first frames get sent between StartGameplay() and player load complete)
2022-02-24 01:18:35 +08:00
AddStep ( "send frames at gameplay start" , ( ) = > getInstance ( PLAYER_1_ID ) . OnGameplayStarted + = ( ) = > SpectatorClient . SendFramesFromUser ( PLAYER_1_ID , 100 ) ) ;
2021-08-11 17:16:25 +08:00
AddUntilStep ( "wait for player load" , ( ) = > spectatorScreen . AllPlayersLoaded ) ;
2022-08-24 16:17:59 +08:00
AddUntilStep ( "wait for clock running" , ( ) = > getInstance ( PLAYER_1_ID ) . SpectatorPlayerClock . IsRunning ) ;
2021-08-11 17:16:25 +08:00
assertNotCatchingUp ( PLAYER_1_ID ) ;
2022-08-26 15:56:03 +08:00
waitUntilRunning ( PLAYER_1_ID ) ;
2021-08-11 17:16:25 +08:00
}
2022-07-07 16:51:49 +08:00
private void loadSpectateScreen ( bool waitForPlayerLoad = true , Action < WorkingBeatmap > ? applyToBeatmap = null )
2021-04-08 21:14:30 +08:00
{
2022-05-12 22:29:28 +08:00
AddStep ( "load screen" , ( ) = >
2021-04-08 21:14:30 +08:00
{
Beatmap . Value = beatmapManager . GetWorkingBeatmap ( importedBeatmap ) ;
Ruleset . Value = importedBeatmap . Ruleset ;
2022-05-12 22:29:28 +08:00
applyToBeatmap ? . Invoke ( Beatmap . Value ) ;
LoadScreen ( spectatorScreen = new MultiSpectatorScreen ( SelectedRoom . Value , playingUsers . ToArray ( ) ) ) ;
2021-04-08 21:14:30 +08:00
} ) ;
2021-04-22 22:39:02 +08:00
AddUntilStep ( "wait for screen load" , ( ) = > spectatorScreen . LoadState = = LoadState . Loaded & & ( ! waitForPlayerLoad | | spectatorScreen . AllPlayersLoaded ) ) ;
2021-04-08 21:14:30 +08:00
}
2021-08-11 17:16:25 +08:00
private void start ( int userId , int? beatmapId = null ) = > start ( new [ ] { userId } , beatmapId ) ;
2022-09-07 19:00:24 +08:00
private void start ( int [ ] userIds , int? beatmapId = null , APIMod [ ] ? mods = null )
2021-04-08 21:14:30 +08:00
{
AddStep ( "start play" , ( ) = >
{
foreach ( int id in userIds )
{
2021-08-25 16:30:37 +08:00
var user = new MultiplayerRoomUser ( id )
{
2021-11-04 17:02:44 +08:00
User = new APIUser { Id = id } ,
2022-09-07 19:00:24 +08:00
Mods = mods ? ? Array . Empty < APIMod > ( ) ,
2021-08-25 16:30:37 +08:00
} ;
2021-08-09 18:05:23 +08:00
2022-09-07 19:00:24 +08:00
OnlinePlayDependencies . MultiplayerClient . AddUser ( user , true ) ;
SpectatorClient . SendStartPlay ( id , beatmapId ? ? importedBeatmapId , mods ) ;
2021-08-25 16:30:37 +08:00
playingUsers . Add ( user ) ;
}
} ) ;
}
2021-08-25 23:28:23 +08:00
private void end ( int userId )
2021-08-25 16:30:37 +08:00
{
2021-08-25 23:28:23 +08:00
AddStep ( $"end play for {userId}" , ( ) = >
2021-08-25 16:30:37 +08:00
{
2021-08-25 23:28:23 +08:00
var user = playingUsers . Single ( u = > u . UserID = = userId ) ;
2021-08-25 16:30:37 +08:00
2022-02-22 16:56:07 +08:00
SpectatorClient . SendEndPlay ( userId ) ;
2022-08-06 10:20:46 +08:00
OnlinePlayDependencies . MultiplayerClient . RemoveUser ( user . User . AsNonNull ( ) ) ;
2021-08-25 16:30:37 +08:00
2021-08-25 23:28:23 +08:00
playingUsers . Remove ( user ) ;
2021-04-08 21:14:30 +08:00
} ) ;
}
2022-08-25 16:30:13 +08:00
/// <summary>
/// Send new frames on behalf of a user.
/// Frames will last for count * 100 milliseconds.
/// </summary>
2021-04-08 21:14:30 +08:00
private void sendFrames ( int userId , int count = 10 ) = > sendFrames ( new [ ] { userId } , count ) ;
private void sendFrames ( int [ ] userIds , int count = 10 )
{
AddStep ( "send frames" , ( ) = >
{
foreach ( int id in userIds )
2022-02-24 01:18:35 +08:00
SpectatorClient . SendFramesFromUser ( id , count ) ;
2021-04-08 21:14:30 +08:00
} ) ;
}
2022-08-26 15:56:03 +08:00
private void checkRunningInstant ( int userId )
{
waitUntilRunning ( userId ) ;
// Todo: The following should work, but is broken because SpectatorScreen retrieves the WorkingBeatmap via the BeatmapManager, bypassing the test scene clock and running real-time.
// AddAssert($"{userId} is {(state ? "paused" : "playing")}", () => getPlayer(userId).ChildrenOfType<GameplayClockContainer>().First().GameplayClock.IsRunning != state);
}
2021-04-22 21:59:47 +08:00
2022-08-26 15:56:03 +08:00
private void checkPausedInstant ( int userId )
2021-06-29 21:45:18 +08:00
{
2022-08-26 15:56:03 +08:00
waitUntilPaused ( userId ) ;
2021-06-29 21:45:18 +08:00
// Todo: The following should work, but is broken because SpectatorScreen retrieves the WorkingBeatmap via the BeatmapManager, bypassing the test scene clock and running real-time.
// AddAssert($"{userId} is {(state ? "paused" : "playing")}", () => getPlayer(userId).ChildrenOfType<GameplayClockContainer>().First().GameplayClock.IsRunning != state);
}
2021-04-22 21:59:47 +08:00
2022-08-25 16:30:13 +08:00
private void assertOnePlayerNotMuted ( ) = > AddAssert ( nameof ( assertOnePlayerNotMuted ) , ( ) = > spectatorScreen . ChildrenOfType < PlayerArea > ( ) . Count ( p = > ! p . Mute ) = = 1 ) ;
2021-06-29 21:53:31 +08:00
2021-04-26 18:01:30 +08:00
private void assertMuted ( int userId , bool muted )
2022-08-25 16:30:13 +08:00
= > AddAssert ( $"{nameof(assertMuted)}({userId}, {muted})" , ( ) = > getInstance ( userId ) . Mute = = muted ) ;
2021-04-08 21:14:30 +08:00
2021-08-11 17:16:25 +08:00
private void assertRunning ( int userId )
2022-08-25 16:30:13 +08:00
= > AddAssert ( $"{nameof(assertRunning)}({userId})" , ( ) = > getInstance ( userId ) . SpectatorPlayerClock . IsRunning ) ;
2021-08-11 17:16:25 +08:00
2022-08-26 15:56:03 +08:00
private void waitUntilPaused ( int userId )
= > AddUntilStep ( $"{nameof(waitUntilPaused)}({userId})" , ( ) = > ! getPlayer ( userId ) . ChildrenOfType < GameplayClockContainer > ( ) . First ( ) . IsRunning ) ;
private void waitUntilRunning ( int userId )
= > AddUntilStep ( $"{nameof(waitUntilRunning)}({userId})" , ( ) = > getPlayer ( userId ) . ChildrenOfType < GameplayClockContainer > ( ) . First ( ) . IsRunning ) ;
2022-08-25 16:58:15 +08:00
2021-08-11 17:16:25 +08:00
private void assertNotCatchingUp ( int userId )
2022-08-25 16:30:13 +08:00
= > AddAssert ( $"{nameof(assertNotCatchingUp)}({userId})" , ( ) = > ! getInstance ( userId ) . SpectatorPlayerClock . IsCatchingUp ) ;
2021-08-11 17:16:25 +08:00
2021-04-22 21:59:47 +08:00
private void waitForCatchup ( int userId )
2022-08-25 16:30:13 +08:00
= > AddUntilStep ( $"{nameof(waitForCatchup)}({userId})" , ( ) = > ! getInstance ( userId ) . SpectatorPlayerClock . IsCatchingUp ) ;
2021-04-08 21:14:30 +08:00
private Player getPlayer ( int userId ) = > getInstance ( userId ) . ChildrenOfType < Player > ( ) . Single ( ) ;
2021-04-22 22:52:22 +08:00
private PlayerArea getInstance ( int userId ) = > spectatorScreen . ChildrenOfType < PlayerArea > ( ) . Single ( p = > p . UserId = = userId ) ;
2021-08-25 16:30:37 +08:00
2022-09-13 15:56:19 +08:00
private GameplayLeaderboardScore getLeaderboardScore ( int userId ) = > spectatorScreen . ChildrenOfType < GameplayLeaderboardScore > ( ) . Single ( s = > s . User ? . OnlineID = = userId ) ;
2021-08-25 23:25:31 +08:00
private int [ ] getPlayerIds ( int count ) = > Enumerable . Range ( PLAYER_1_ID , count ) . ToArray ( ) ;
2021-04-08 21:14:30 +08:00
}
}