Пример #1
0
        public void TestTeamDisplay()
        {
            AddStep("start players", () =>
            {
                var player1 = OnlinePlayDependencies.Client.AddUser(new User {
                    Id = PLAYER_1_ID
                }, true);
                player1.MatchState = new TeamVersusUserState
                {
                    TeamID = 0,
                };

                var player2 = OnlinePlayDependencies.Client.AddUser(new User {
                    Id = PLAYER_2_ID
                }, true);
                player2.MatchState = new TeamVersusUserState
                {
                    TeamID = 1,
                };

                SpectatorClient.StartPlay(player1.UserID, importedBeatmapId);
                SpectatorClient.StartPlay(player2.UserID, importedBeatmapId);

                playingUsers.Add(player1);
                playingUsers.Add(player2);
            });

            loadSpectateScreen();

            sendFrames(PLAYER_1_ID, 1000);
            sendFrames(PLAYER_2_ID, 1000);

            AddWaitStep("wait a bit", 20);
        }
Пример #2
0
        public void TestDelayedStart()
        {
            AddStep("start players silently", () =>
            {
                OnlinePlayDependencies.Client.AddUser(new User {
                    Id = PLAYER_1_ID
                }, true);
                OnlinePlayDependencies.Client.AddUser(new User {
                    Id = PLAYER_2_ID
                }, true);

                playingUsers.Add(new MultiplayerRoomUser(PLAYER_1_ID));
                playingUsers.Add(new MultiplayerRoomUser(PLAYER_2_ID));
            });

            loadSpectateScreen(false);

            AddWaitStep("wait a bit", 10);
            AddStep("load player first_player_id", () => SpectatorClient.StartPlay(PLAYER_1_ID, importedBeatmapId));
            AddUntilStep("one player added", () => spectatorScreen.ChildrenOfType <Player>().Count() == 1);

            AddWaitStep("wait a bit", 10);
            AddStep("load player second_player_id", () => SpectatorClient.StartPlay(PLAYER_2_ID, importedBeatmapId));
            AddUntilStep("two players added", () => spectatorScreen.ChildrenOfType <Player>().Count() == 2);
        }
Пример #3
0
 private void sendFrames(int[] userIds, int count = 10)
 {
     AddStep("send frames", () =>
     {
         foreach (int id in userIds)
         {
             SpectatorClient.SendFrames(id, count);
         }
     });
 }
Пример #4
0
        private void end(int userId)
        {
            AddStep($"end play for {userId}", () =>
            {
                var user = playingUsers.Single(u => u.UserID == userId);

                OnlinePlayDependencies.Client.RemoveUser(user.User.AsNonNull());
                SpectatorClient.EndPlay(userId);

                playingUsers.Remove(user);
            });
        }
Пример #5
0
 private void start(int[] userIds, int?beatmapId = null)
 {
     AddStep("start play", () =>
     {
         foreach (int id in userIds)
         {
             Client.CurrentMatchPlayingUserIds.Add(id);
             SpectatorClient.StartPlay(id, beatmapId ?? importedBeatmapId);
             playingUserIds.Add(id);
         }
     });
 }
Пример #6
0
        /// <summary>
        /// </summary>
        public MapLoadingScreen(List <Score> scores, Replay replay = null, SpectatorClient spectatorClient = null)
        {
            Scores          = scores;
            Replay          = replay;
            SpectatorClient = spectatorClient;

            var game   = GameBase.Game as QuaverGame;
            var cursor = game?.GlobalUserInterface.Cursor;

            cursor.Alpha = 0;

            View = new MapLoadingScreenView(this);
            AudioTrack.AllowPlayback = false;
        }
Пример #7
0
        private void start(int[] userIds, int?beatmapId = null)
        {
            AddStep("start play", () =>
            {
                foreach (int id in userIds)
                {
                    OnlinePlayDependencies.Client.AddUser(new User {
                        Id = id
                    }, true);

                    SpectatorClient.StartPlay(id, beatmapId ?? importedBeatmapId);
                    playingUsers.Add(new MultiplayerRoomUser(id));
                }
            });
        }
Пример #8
0
        public new void SetUpSteps()
        {
            AddStep("reset", () =>
            {
                Clear();

                clocks = new Dictionary <int, ManualClock>
                {
                    { PLAYER_1_ID, new ManualClock() },
                    { PLAYER_2_ID, new ManualClock() }
                };

                foreach (var(userId, _) in clocks)
                {
                    SpectatorClient.StartPlay(userId, 0);
                }
            });
Пример #9
0
        public new void SetUpSteps()
        {
            AddStep("reset", () =>
            {
                Clear();

                clocks = new Dictionary <int, ManualClock>
                {
                    { PLAYER_1_ID, new ManualClock() },
                    { PLAYER_2_ID, new ManualClock() }
                };

                foreach ((int userId, var _) in clocks)
                {
                    SpectatorClient.SendStartPlay(userId, 0);
                    OnlinePlayDependencies.MultiplayerClient.AddUser(new APIUser {
                        Id = userId
                    });
                }
            });

            AddStep("create leaderboard", () =>
            {
                Beatmap.Value      = CreateWorkingBeatmap(Ruleset.Value);
                var playable       = Beatmap.Value.GetPlayableBeatmap(Ruleset.Value);
                var scoreProcessor = new OsuScoreProcessor();
                scoreProcessor.ApplyBeatmap(playable);

                LoadComponentAsync(leaderboard = new MultiSpectatorLeaderboard(Ruleset.Value, scoreProcessor, clocks.Keys.Select(id => new MultiplayerRoomUser(id)).ToArray())
                {
                    Expanded = { Value = true }
                }, Add);
            });

            AddUntilStep("wait for load", () => leaderboard.IsLoaded);
            AddUntilStep("wait for user population", () => leaderboard.ChildrenOfType <GameplayLeaderboardScore>().Count() == 2);

            AddStep("add clock sources", () =>
            {
                foreach ((int userId, var clock) in clocks)
                {
                    leaderboard.AddClock(userId, clock);
                }
            });
        }
Пример #10
0
        public void TestDelayedStart()
        {
            AddStep("start players silently", () =>
            {
                Client.CurrentMatchPlayingUserIds.Add(PLAYER_1_ID);
                Client.CurrentMatchPlayingUserIds.Add(PLAYER_2_ID);
                playingUserIds.Add(PLAYER_1_ID);
                playingUserIds.Add(PLAYER_2_ID);
            });

            loadSpectateScreen(false);

            AddWaitStep("wait a bit", 10);
            AddStep("load player first_player_id", () => SpectatorClient.StartPlay(PLAYER_1_ID, importedBeatmapId));
            AddUntilStep("one player added", () => spectatorScreen.ChildrenOfType <Player>().Count() == 1);

            AddWaitStep("wait a bit", 10);
            AddStep("load player second_player_id", () => SpectatorClient.StartPlay(PLAYER_2_ID, importedBeatmapId));
            AddUntilStep("two players added", () => spectatorScreen.ChildrenOfType <Player>().Count() == 2);
        }
Пример #11
0
        public void TestLeaderboardTracksCurrentTime()
        {
            AddStep("send frames", () =>
            {
                // For player 1, send frames in sets of 1.
                // For player 2, send frames in sets of 10.
                for (int i = 0; i < 100; i++)
                {
                    SpectatorClient.SendFramesFromUser(PLAYER_1_ID, 1);

                    if (i % 10 == 0)
                    {
                        SpectatorClient.SendFramesFromUser(PLAYER_2_ID, 10);
                    }
                }
            });

            assertCombo(PLAYER_1_ID, 1);
            assertCombo(PLAYER_2_ID, 10);

            // Advance to a point where only user player 1's frame changes.
            setTime(500);
            assertCombo(PLAYER_1_ID, 5);
            assertCombo(PLAYER_2_ID, 10);

            // Advance to a point where both user's frame changes.
            setTime(1100);
            assertCombo(PLAYER_1_ID, 11);
            assertCombo(PLAYER_2_ID, 20);

            // Advance user player 2 only to a point where its frame changes.
            setTime(PLAYER_2_ID, 2100);
            assertCombo(PLAYER_1_ID, 11);
            assertCombo(PLAYER_2_ID, 30);

            // Advance both users beyond their last frame
            setTime(101 * 100);
            assertCombo(PLAYER_1_ID, 100);
            assertCombo(PLAYER_2_ID, 100);
        }
Пример #12
0
        public SpectatorDialog(SpectatorClient client)
        {
            SpectatorClient = client;

            Image            = UserInterface.WaitingPanel;
            Size             = new ScalableVector2(450, 134);
            Alpha            = 0;
            SetChildrenAlpha = true;

            Icon = new Sprite
            {
                Parent    = this,
                Alignment = Alignment.TopCenter,
                Image     = FontAwesome.Get(FontAwesomeIcon.fa_information_button),
                Y         = 18,
                Size      = new ScalableVector2(24, 24)
            };

            // ReSharper disable once ObjectCreationAsStatement
            Text = new SpriteTextBitmap(FontsBitmap.AllerRegular, "Waiting for host!")
            {
                Parent    = this,
                FontSize  = 20,
                Y         = Icon.Y + Icon.Height + 10,
                Alignment = Alignment.TopCenter
            };

            LoadingWheel = new Sprite()
            {
                Parent    = this,
                Size      = new ScalableVector2(40, 40),
                Image     = UserInterface.LoadingWheel,
                Alignment = Alignment.TopCenter,
                Y         = Text.Y + Text.Height + 10
            };

            OnlineManager.Client.OnUserStatusReceived += OnClientStatusReceived;
        }
Пример #13
0
        private void load(ReadableKeyCombinationProvider keyCombinationProvider)
        {
            try
            {
                using (var str = File.OpenRead(typeof(OsuGameBase).Assembly.Location))
                    VersionHash = str.ComputeMD5Hash();
            }
            catch
            {
                // special case for android builds, which can't read DLLs from a packed apk.
                // should eventually be handled in a better way.
                VersionHash = $"{Version}-{RuntimeInfo.OS}".ComputeMD5Hash();
            }

            Resources.AddStore(new DllResourceStore(OsuResources.ResourceAssembly));

            if (Storage.Exists(DatabaseContextFactory.DATABASE_NAME))
            {
                dependencies.Cache(EFContextFactory = new DatabaseContextFactory(Storage));
            }

            dependencies.Cache(realm = new RealmAccess(Storage, "client", EFContextFactory));

            dependencies.CacheAs <RulesetStore>(RulesetStore = new RealmRulesetStore(realm, Storage));
            dependencies.CacheAs <IRulesetStore>(RulesetStore);

            Decoder.RegisterDependencies(RulesetStore);

            // Backup is taken here rather than in EFToRealmMigrator to avoid recycling realm contexts
            // after initial usages below. It can be moved once a direction is established for handling re-subscription.
            // See https://github.com/ppy/osu/pull/16547 for more discussion.
            if (EFContextFactory != null)
            {
                const string backup_folder = "backups";

                string migration = $"before_final_migration_{DateTimeOffset.UtcNow.ToUnixTimeSeconds()}";

                EFContextFactory.CreateBackup(Path.Combine(backup_folder, $"client.{migration}.db"));
                realm.CreateBackup(Path.Combine(backup_folder, $"client.{migration}.realm"));

                using (var source = Storage.GetStream("collection.db"))
                {
                    if (source != null)
                    {
                        using (var destination = Storage.GetStream(Path.Combine(backup_folder, $"collection.{migration}.db"), FileAccess.Write, FileMode.CreateNew))
                            source.CopyTo(destination);
                    }
                }
            }

            dependencies.CacheAs(Storage);

            var largeStore = new LargeTextureStore(Host.CreateTextureLoaderStore(new NamespacedResourceStore <byte[]>(Resources, @"Textures")));

            largeStore.AddStore(Host.CreateTextureLoaderStore(new OnlineStore()));
            dependencies.Cache(largeStore);

            dependencies.CacheAs(this);
            dependencies.CacheAs(LocalConfig);

            InitialiseFonts();

            Audio.Samples.PlaybackConcurrency = SAMPLE_CONCURRENCY;

            dependencies.Cache(SkinManager = new SkinManager(Storage, realm, Host, Resources, Audio, Scheduler));
            dependencies.CacheAs <ISkinSource>(SkinManager);

            EndpointConfiguration endpoints = UseDevelopmentServer ? (EndpointConfiguration) new DevelopmentEndpointConfiguration() : new ProductionEndpointConfiguration();

            MessageFormatter.WebsiteRootUrl = endpoints.WebsiteRootUrl;

            dependencies.CacheAs(API ??= new APIAccess(LocalConfig, endpoints, VersionHash));

            dependencies.CacheAs(spectatorClient   = new OnlineSpectatorClient(endpoints));
            dependencies.CacheAs(multiplayerClient = new OnlineMultiplayerClient(endpoints));

            var defaultBeatmap = new DummyWorkingBeatmap(Audio, Textures);

            // ordering is important here to ensure foreign keys rules are not broken in ModelStore.Cleanup()
            dependencies.Cache(ScoreManager   = new ScoreManager(RulesetStore, () => BeatmapManager, Storage, realm, Scheduler, () => difficultyCache, LocalConfig));
            dependencies.Cache(BeatmapManager = new BeatmapManager(Storage, realm, RulesetStore, API, Audio, Resources, Host, defaultBeatmap, performOnlineLookups: true));

            dependencies.Cache(BeatmapDownloader = new BeatmapModelDownloader(BeatmapManager, API));
            dependencies.Cache(ScoreDownloader   = new ScoreModelDownloader(ScoreManager, API));

            dependencies.Cache(difficultyCache = new BeatmapDifficultyCache());
            AddInternal(difficultyCache);

            dependencies.Cache(userCache = new UserLookupCache());
            AddInternal(userCache);

            dependencies.Cache(beatmapCache = new BeatmapLookupCache());
            AddInternal(beatmapCache);

            var scorePerformanceManager = new ScorePerformanceCache();

            dependencies.Cache(scorePerformanceManager);
            AddInternal(scorePerformanceManager);

            dependencies.CacheAs <IRulesetConfigCache>(rulesetConfigCache = new RulesetConfigCache(realm, RulesetStore));

            var powerStatus = CreateBatteryInfo();

            if (powerStatus != null)
            {
                dependencies.CacheAs(powerStatus);
            }

            dependencies.Cache(SessionStatics = new SessionStatics());
            dependencies.Cache(new OsuColour());

            RegisterImportHandler(BeatmapManager);
            RegisterImportHandler(ScoreManager);
            RegisterImportHandler(SkinManager);

            // drop track volume game-wide to leave some head-room for UI effects / samples.
            // this means that for the time being, gameplay sample playback is louder relative to the audio track, compared to stable.
            // we may want to revisit this if users notice or complain about the difference (consider this a bit of a trial).
            Audio.Tracks.AddAdjustment(AdjustableProperty.Volume, globalTrackVolumeAdjust);

            Beatmap = new NonNullableBindable <WorkingBeatmap>(defaultBeatmap);

            dependencies.CacheAs <IBindable <WorkingBeatmap> >(Beatmap);
            dependencies.CacheAs(Beatmap);

            // add api components to hierarchy.
            if (API is APIAccess apiAccess)
            {
                AddInternal(apiAccess);
            }
            AddInternal(spectatorClient);
            AddInternal(multiplayerClient);

            AddInternal(rulesetConfigCache);

            GlobalActionContainer globalBindings;

            base.Content.Add(new SafeAreaContainer
            {
                SafeAreaOverrideEdges = SafeAreaOverrideEdges,
                RelativeSizeAxes      = Axes.Both,
                Child = CreateScalingContainer().WithChildren(new Drawable[]
                {
                    (MenuCursorContainer = new MenuCursorContainer
                    {
                        RelativeSizeAxes = Axes.Both
                    }).WithChild(content = new OsuTooltipContainer(MenuCursorContainer.Cursor)
                    {
                        RelativeSizeAxes = Axes.Both
                    }),
                    // to avoid positional input being blocked by children, ensure the GlobalActionContainer is above everything.
                    globalBindings = new GlobalActionContainer(this)
                })
            });

            KeyBindingStore = new RealmKeyBindingStore(realm, keyCombinationProvider);
            KeyBindingStore.Register(globalBindings, RulesetStore.AvailableRulesets);

            dependencies.Cache(globalBindings);

            PreviewTrackManager previewTrackManager;

            dependencies.Cache(previewTrackManager = new PreviewTrackManager(BeatmapManager.BeatmapTrackStore));
            Add(previewTrackManager);

            AddInternal(MusicController = new MusicController());
            dependencies.CacheAs(MusicController);

            Ruleset.BindValueChanged(onRulesetChanged);
            Beatmap.BindValueChanged(onBeatmapChanged);
        }
Пример #14
0
        public void TestNegativeGameplayStartTime()
        {
            start(PLAYER_1_ID);

            loadSpectateScreen(false, -500);

            // 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)
            AddStep("send frames at gameplay start", () => getInstance(PLAYER_1_ID).OnGameplayStarted += () => SpectatorClient.SendFrames(PLAYER_1_ID, 100));

            AddUntilStep("wait for player load", () => spectatorScreen.AllPlayersLoaded);

            AddWaitStep("wait for progression", 3);

            assertNotCatchingUp(PLAYER_1_ID);
            assertRunning(PLAYER_1_ID);
        }
Пример #15
0
        /// <summary>
        ///     Ctor -
        /// </summary>
        /// <param name="map"></param>
        /// <param name="md5"></param>
        /// <param name="scores"></param>
        /// <param name="replay"></param>
        /// <param name="isPlayTesting"></param>
        /// <param name="playTestTime"></param>
        /// <param name="isCalibratingOffset"></param>
        /// <param name="spectatorClient"></param>
        public GameplayScreen(Qua map, string md5, List <Score> scores, Replay replay = null, bool isPlayTesting = false, double playTestTime = 0,
                              bool isCalibratingOffset = false, SpectatorClient spectatorClient = null)
        {
            TimePlayed = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();

            if (isPlayTesting)
            {
                var testingQua = ObjectHelper.DeepClone(map);
                testingQua.HitObjects.RemoveAll(x => x.StartTime < playTestTime);
                Qua.RestoreDefaultValues(testingQua);

                Map = testingQua;
                OriginalEditorMap = map;
            }
            else
            {
                Map = map;
            }

            LocalScores         = scores;
            MapHash             = md5;
            LoadedReplay        = replay;
            IsPlayTesting       = isPlayTesting;
            PlayTestAudioTime   = playTestTime;
            IsCalibratingOffset = isCalibratingOffset;
            IsMultiplayerGame   = OnlineManager.CurrentGame != null;
            SpectatorClient     = spectatorClient;

            if (SpectatorClient != null)
            {
                LoadedReplay = SpectatorClient.Replay;
            }

            if (IsMultiplayerGame)
            {
                OnlineManager.Client.OnUserJoinedGame    += OnUserJoinedGame;
                OnlineManager.Client.OnUserLeftGame      += OnUserLeftGame;
                OnlineManager.Client.OnAllPlayersLoaded  += OnAllPlayersLoaded;
                OnlineManager.Client.OnAllPlayersSkipped += OnAllPlayersSkipped;
            }

            Timing = new GameplayAudioTiming(this);

            // Initialize the custom audio sample cache and the sound effect index.
            if (!IsCalibratingOffset)
            {
                CustomAudioSampleCache.LoadSamples(MapManager.Selected.Value, MapHash);
            }

            NextSoundEffectIndex = 0;
            UpdateNextSoundEffectIndex();

            // Remove paused modifier if enabled.
            if (ModManager.IsActivated(ModIdentifier.Paused))
            {
                ModManager.RemoveMod(ModIdentifier.Paused);
            }

            // Handle autoplay replays.
            if (ModManager.IsActivated(ModIdentifier.Autoplay))
            {
                LoadedReplay = ReplayHelper.GeneratePerfectReplay(map, MapHash);
            }

            // Determine if we're in replay mode.
            if (LoadedReplay != null)
            {
                InReplayMode = true;
            }

            // Create the current replay that will be captured.
            ReplayCapturer = new ReplayCapturer(this);

            SetRuleset();
            SetRichPresence();

            AudioTrack.AllowPlayback = true;

            if (IsCalibratingOffset)
            {
                Metronome = new Metronome(map);
            }

            View = new GameplayScreenView(this);
        }
Пример #16
0
        private void load()
        {
            try
            {
                using (var str = File.OpenRead(typeof(OsuGameBase).Assembly.Location))
                    VersionHash = str.ComputeMD5Hash();
            }
            catch
            {
                // special case for android builds, which can't read DLLs from a packed apk.
                // should eventually be handled in a better way.
                VersionHash = $"{Version}-{RuntimeInfo.OS}".ComputeMD5Hash();
            }

            Resources.AddStore(new DllResourceStore(OsuResources.ResourceAssembly));

            dependencies.Cache(contextFactory = new DatabaseContextFactory(Storage));

            dependencies.Cache(realmFactory = new RealmContextFactory(Storage));

            updateThreadState = Host.UpdateThread.State.GetBoundCopy();
            updateThreadState.BindValueChanged(updateThreadStateChanged);

            AddInternal(realmFactory);

            dependencies.CacheAs(Storage);

            var largeStore = new LargeTextureStore(Host.CreateTextureLoaderStore(new NamespacedResourceStore <byte[]>(Resources, @"Textures")));

            largeStore.AddStore(Host.CreateTextureLoaderStore(new OnlineStore()));
            dependencies.Cache(largeStore);

            dependencies.CacheAs(this);
            dependencies.CacheAs(LocalConfig);

            InitialiseFonts();

            Audio.Samples.PlaybackConcurrency = SAMPLE_CONCURRENCY;

            runMigrations();

            dependencies.Cache(SkinManager = new SkinManager(Storage, contextFactory, Host, Resources, Audio));
            dependencies.CacheAs <ISkinSource>(SkinManager);

            // needs to be done here rather than inside SkinManager to ensure thread safety of CurrentSkinInfo.
            SkinManager.ItemRemoved.BindValueChanged(weakRemovedInfo =>
            {
                if (weakRemovedInfo.NewValue.TryGetTarget(out var removedInfo))
                {
                    Schedule(() =>
                    {
                        // check the removed skin is not the current user choice. if it is, switch back to default.
                        if (removedInfo.ID == SkinManager.CurrentSkinInfo.Value.ID)
                        {
                            SkinManager.CurrentSkinInfo.Value = SkinInfo.Default;
                        }
                    });
                }
            });

            EndpointConfiguration endpoints = UseDevelopmentServer ? (EndpointConfiguration) new DevelopmentEndpointConfiguration() : new ProductionEndpointConfiguration();

            MessageFormatter.WebsiteRootUrl = endpoints.WebsiteRootUrl;

            dependencies.CacheAs(API ??= new APIAccess(LocalConfig, endpoints, VersionHash));

            dependencies.CacheAs(spectatorClient   = new OnlineSpectatorClient(endpoints));
            dependencies.CacheAs(multiplayerClient = new OnlineMultiplayerClient(endpoints));

            var defaultBeatmap = new DummyWorkingBeatmap(Audio, Textures);

            dependencies.Cache(RulesetStore = new RulesetStore(contextFactory, Storage));
            dependencies.Cache(fileStore    = new FileStore(contextFactory, Storage));

            // ordering is important here to ensure foreign keys rules are not broken in ModelStore.Cleanup()
            dependencies.Cache(ScoreManager   = new ScoreManager(RulesetStore, () => BeatmapManager, Storage, API, contextFactory, Scheduler, Host, () => difficultyCache, LocalConfig));
            dependencies.Cache(BeatmapManager = new BeatmapManager(Storage, contextFactory, RulesetStore, API, Audio, Resources, Host, defaultBeatmap, true));

            // this should likely be moved to ArchiveModelManager when another case appears where it is necessary
            // to have inter-dependent model managers. this could be obtained with an IHasForeign<T> interface to
            // allow lookups to be done on the child (ScoreManager in this case) to perform the cascading delete.
            List <ScoreInfo> getBeatmapScores(BeatmapSetInfo set)
            {
                var beatmapIds = BeatmapManager.QueryBeatmaps(b => b.BeatmapSetInfoID == set.ID).Select(b => b.ID).ToList();

                return(ScoreManager.QueryScores(s => beatmapIds.Contains(s.Beatmap.ID)).ToList());
            }

            BeatmapManager.ItemRemoved.BindValueChanged(i =>
            {
                if (i.NewValue.TryGetTarget(out var item))
                {
                    ScoreManager.Delete(getBeatmapScores(item), true);
                }
            });

            BeatmapManager.ItemUpdated.BindValueChanged(i =>
            {
                if (i.NewValue.TryGetTarget(out var item))
                {
                    ScoreManager.Undelete(getBeatmapScores(item), true);
                }
            });

            dependencies.Cache(difficultyCache = new BeatmapDifficultyCache());
            AddInternal(difficultyCache);

            dependencies.Cache(userCache = new UserLookupCache());
            AddInternal(userCache);

            var scorePerformanceManager = new ScorePerformanceCache();

            dependencies.Cache(scorePerformanceManager);
            AddInternal(scorePerformanceManager);

            migrateDataToRealm();

            dependencies.Cache(rulesetConfigCache = new RulesetConfigCache(realmFactory, RulesetStore));

            var powerStatus = CreateBatteryInfo();

            if (powerStatus != null)
            {
                dependencies.CacheAs(powerStatus);
            }

            dependencies.Cache(SessionStatics = new SessionStatics());
            dependencies.Cache(new OsuColour());

            RegisterImportHandler(BeatmapManager);
            RegisterImportHandler(ScoreManager);
            RegisterImportHandler(SkinManager);

            // drop track volume game-wide to leave some head-room for UI effects / samples.
            // this means that for the time being, gameplay sample playback is louder relative to the audio track, compared to stable.
            // we may want to revisit this if users notice or complain about the difference (consider this a bit of a trial).
            Audio.Tracks.AddAdjustment(AdjustableProperty.Volume, globalTrackVolumeAdjust);

            Beatmap = new NonNullableBindable <WorkingBeatmap>(defaultBeatmap);

            dependencies.CacheAs <IBindable <WorkingBeatmap> >(Beatmap);
            dependencies.CacheAs(Beatmap);

            fileStore.Cleanup();

            // add api components to hierarchy.
            if (API is APIAccess apiAccess)
            {
                AddInternal(apiAccess);
            }
            AddInternal(spectatorClient);
            AddInternal(multiplayerClient);

            AddInternal(rulesetConfigCache);

            GlobalActionContainer globalBindings;

            var mainContent = new Drawable[]
            {
                MenuCursorContainer = new MenuCursorContainer {
                    RelativeSizeAxes = Axes.Both
                },
                // to avoid positional input being blocked by children, ensure the GlobalActionContainer is above everything.
                globalBindings = new GlobalActionContainer(this)
            };

            MenuCursorContainer.Child = content = new OsuTooltipContainer(MenuCursorContainer.Cursor)
            {
                RelativeSizeAxes = Axes.Both
            };

            base.Content.Add(CreateScalingContainer().WithChildren(mainContent));

            KeyBindingStore = new RealmKeyBindingStore(realmFactory);
            KeyBindingStore.Register(globalBindings, RulesetStore.AvailableRulesets);

            dependencies.Cache(globalBindings);

            PreviewTrackManager previewTrackManager;

            dependencies.Cache(previewTrackManager = new PreviewTrackManager());
            Add(previewTrackManager);

            AddInternal(MusicController = new MusicController());
            dependencies.CacheAs(MusicController);

            Ruleset.BindValueChanged(onRulesetChanged);
        }
        private void testLeadIn(Action <WorkingBeatmap> applyToBeatmap = null)
        {
            start(PLAYER_1_ID);

            loadSpectateScreen(false, applyToBeatmap);

            // 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)
            AddStep("send frames at gameplay start", () => getInstance(PLAYER_1_ID).OnGameplayStarted += () => SpectatorClient.SendFramesFromUser(PLAYER_1_ID, 100));

            AddUntilStep("wait for player load", () => spectatorScreen.AllPlayersLoaded);

            AddWaitStep("wait for progression", 3);

            assertNotCatchingUp(PLAYER_1_ID);
            assertRunning(PLAYER_1_ID);
        }