static bool Prefix(ref BeatmapIdentifierNetSerializable beatmapId, ref GameplayModifiers gameplayModifiers, ref float initialStartTime, MultiplayerLevelLoader __instance)
        {
            if (!beatmapId.levelID.StartsWith(CustomLevelPrefix) || SongCore.Loader.GetLevelById(beatmapId.levelID) != null)
            {
                return(true);
            }
            MultiplayerLevelLoader = __instance;
            string levelId = beatmapId?.levelID;
            BeatmapIdentifierNetSerializable bmId = beatmapId;
            GameplayModifiers modifiers           = gameplayModifiers;
            float             startTime           = initialStartTime;

            if (levelId != null && levelId.StartsWith(CustomLevelPrefix))
            {
                if (SongCore.Loader.GetLevelById(levelId) != null)
                {
                    LoadingLevelId = null;
                    return(true);
                }
                if (LoadingLevelId == null || LoadingLevelId != levelId)
                {
                    LoadingLevelId = levelId;
                    var downloadTask = Downloader.TryDownloadSong(levelId, CancellationToken.None, r =>
                    {
                        if (r)
                        {
                            //Plugin.Log?.Debug($"Triggering 'LobbyGameStateController.HandleMenuRpcManagerStartedLevel' after level download.");
                            //LobbyGameStateController_HandleMenuRpcManagerStartedLevel.LobbyGameStateController.HandleMenuRpcManagerStartedLevel(LobbyGameStateController_HandleMenuRpcManagerStartedLevel.LastUserId, bmId, modifiers, startTime);
                        }
                        else
                        {
                            Plugin.Log?.Warn($"TryDownloadSong was unsuccessful.");
                        }
                        MultiplayerLevelLoader.LoadLevel(bmId, modifiers, startTime);
                        LoadingLevelId = null;
                    });
                    return(false);
                }
            }
            LoadingLevelId = null;
            return(true);
        }
        static bool Prefix(string userId, BeatmapIdentifierNetSerializable beatmapId, LobbyPlayersDataModel __instance)
        {
            if (beatmapId != null)
            {
                if (beatmapId.levelID.StartsWith("custom_level_"))
                {
                    Plugin.Log?.Debug($"'{userId}' selected song '{beatmapId.levelID}'");
                    if (SongCore.Loader.GetLevelById(beatmapId.levelID) != null)
                    {
                        Plugin.Log?.Debug($"Custom song '{beatmapId.levelID}' loaded.");
                        return(true);
                    }

                    Plugin.Log?.Debug("getting characteristics");
                    var beatmapCharacteristicCollection = __instance.GetField <BeatmapCharacteristicCollectionSO, LobbyPlayersDataModel>("_beatmapCharacteristicCollection");
                    Plugin.Log?.Debug("setting preview");
                    __instance.SetPlayerBeatmapLevel(userId, new OverrideClasses.PreviewBeatmapLevelStub(beatmapId.levelID), beatmapId.difficulty,
                                                     beatmapCharacteristicCollection.GetBeatmapCharacteristicBySerializedName(beatmapId.beatmapCharacteristicSerializedName));

                    return(false);
                }
            }
            return(true);
        }
        public async override void HandleMenuRpcManagerSelectedBeatmap(string userId, BeatmapIdentifierNetSerializable beatmapId)
        {
            if (beatmapId != null)
            {
                string?hash = Utilities.Utils.LevelIdToHash(beatmapId.levelID);
                if (hash != null)
                {
                    Plugin.Log?.Debug($"'{userId}' selected song '{hash}'.");
                    BeatmapCharacteristicSO characteristic = _beatmapCharacteristicCollection.GetBeatmapCharacteristicBySerializedName(beatmapId.beatmapCharacteristicSerializedName);
                    PreviewBeatmapStub?     preview        = null;

                    if (_playersData.Values.Any(playerData => playerData.beatmapLevel?.levelID == beatmapId.levelID))
                    {
                        IPreviewBeatmapLevel playerPreview = _playersData.Values.Where(playerData => playerData.beatmapLevel?.levelID == beatmapId.levelID).First().beatmapLevel;
                        if (playerPreview is PreviewBeatmapStub playerPreviewStub)
                        {
                            preview = playerPreviewStub;
                        }
                    }

                    IPreviewBeatmapLevel localPreview = SongCore.Loader.GetLevelById(beatmapId.levelID);
                    if (localPreview != null)
                    {
                        preview = new PreviewBeatmapStub(hash, localPreview);
                    }

                    if (preview == null)
                    {
                        try
                        {
                            Beatmap bm = await Plugin.BeatSaver.Hash(hash);

                            preview = new PreviewBeatmapStub(bm);
                        }
                        catch
                        {
                            return;
                        }
                    }

                    if (userId == base.hostUserId)
                    {
                        _sessionManager.SetLocalPlayerState("beatmap_downloaded", preview.isDownloaded);
                    }

                    HMMainThreadDispatcher.instance.Enqueue(() => base.SetPlayerBeatmapLevel(userId, preview, beatmapId.difficulty, characteristic));
                    return;
                }
            }

            base.HandleMenuRpcManagerSelectedBeatmap(userId, beatmapId);
        }
        /// <summary>
        /// Triggered when a player selects a song using a vanilla packet.
        /// </summary>
        public async override void HandleMenuRpcManagerSelectedBeatmap(string userId, BeatmapIdentifierNetSerializable beatmapId)
        {
            OnSelectedBeatmap(userId, beatmapId);
            string?hash = Utilities.Utils.LevelIdToHash(beatmapId.levelID);

            Plugin.Log?.Debug($"'{userId}' selected song '{hash ?? beatmapId.levelID}'.");
            if (hash != null)
            {
                BeatmapCharacteristicSO characteristic = _beatmapCharacteristicCollection.GetBeatmapCharacteristicBySerializedName(beatmapId.beatmapCharacteristicSerializedName);
                if (_playersData.Values.Any(playerData => playerData.beatmapLevel?.levelID == beatmapId.levelID))
                {
                    PreviewBeatmapStub?preview = GetExistingPreview(beatmapId.levelID);
                    HMMainThreadDispatcher.instance.Enqueue(() => base.SetPlayerBeatmapLevel(userId, preview, beatmapId.difficulty, characteristic));
                }
                else
                {
                    PreviewBeatmapStub?  preview      = null;
                    IPreviewBeatmapLevel localPreview = SongCore.Loader.GetLevelById(beatmapId.levelID);
                    if (localPreview != null)
                    {
                        preview = new PreviewBeatmapStub(hash, localPreview);
                    }
                    if (preview == null)
                    {
                        preview = await FetchBeatSaverPreview(beatmapId.levelID, hash);
                    }
                    HMMainThreadDispatcher.instance.Enqueue(() => base.SetPlayerBeatmapLevel(userId, preview, beatmapId.difficulty, characteristic));
                }
            }
            else
            {
                base.HandleMenuRpcManagerSelectedBeatmap(userId, beatmapId);
            }
        }
        static bool Prefix(ref BeatmapIdentifierNetSerializable beatmapId, ref GameplayModifiers gameplayModifiers, ref float initialStartTime, MultiplayerLevelLoader __instance)
        {
            string?levelId = beatmapId.levelID;

            if (SongCore.Loader.GetLevelById(levelId) != null)
            {
                if (LoadingLevelId != levelId)
                {
                    Plugin.Log?.Debug($"Level with ID '{levelId}' already exists."); // Don't log if LoadLevel was called when a download finished.
                }
                LoadingLevelId = null;
                return(true);
            }
            string?hash = Utilities.Utils.LevelIdToHash(beatmapId.levelID);

            if (hash == null)
            {
                Plugin.Log?.Info($"Could not get a hash from beatmap with LevelId {beatmapId.levelID}");
                LoadingLevelId = null;
                return(true);
            }
            if (Downloader.TryGetDownload(levelId, out _))
            {
                Plugin.Log?.Debug($"Download for '{levelId}' is already in progress.");
                return(false);
            }
            MultiplayerLevelLoader = __instance;
            BeatmapIdentifierNetSerializable bmId = beatmapId;
            GameplayModifiers modifiers           = gameplayModifiers;
            float             startTime           = initialStartTime;
            // TODO: Link to UI progress bar. Don't forget case where downloaded song may be switched back to a currently downloading one.
            // Probably best to have a class containing a download that the IProgress updates with the current progress.
            // Then that class can be hooked/unhooked from the UI progress bar.
            IProgress <double>?progress = null;

            //IProgress<double> progress = new Progress<double>(p =>
            //{
            //    Plugin.Log?.Debug($"Progress for '{bmId.levelID}': {p:P}");
            //});
            if (LoadingLevelId == null || LoadingLevelId != levelId)
            {
                LoadingLevelId = levelId;

                Plugin.Log?.Debug($"Attempting to download level with ID '{levelId}'...");
                Task?downloadTask = Downloader.TryDownloadSong(levelId, progress, CancellationToken.None).ContinueWith(b =>
                {
                    try
                    {
                        IPreviewBeatmapLevel?level = b.Result;
                        if (level != null)
                        {
                            Plugin.Log?.Debug($"Level with ID '{levelId}' was downloaded successfully.");
                            //Plugin.Log?.Debug($"Triggering 'LobbyGameStateController.HandleMenuRpcManagerStartedLevel' after level download.");
                            //LobbyGameStateController_HandleMenuRpcManagerStartedLevel.LobbyGameStateController.HandleMenuRpcManagerStartedLevel(LobbyGameStateController_HandleMenuRpcManagerStartedLevel.LastUserId, bmId, modifiers, startTime);
                            MultiplayerLevelLoader.LoadLevel(bmId, modifiers, startTime);
                        }
                        else
                        {
                            Plugin.Log?.Warn($"TryDownloadSong was unsuccessful.");
                        }
                    }
                    catch (Exception ex)
                    {
                        Plugin.Log?.Warn($"Error in TryDownloadSong continuation: {ex.Message}");
                        Plugin.Log?.Debug(ex);
                    }
                    finally
                    {
                        LoadingLevelId = null;
                    }
                });
                return(false);
            }
            return(true);
        }