Exemplo n.º 1
0
        /// <summary>
        /// Select a level pack.
        /// </summary>
        /// <param name="levelPackId"></param>
        public void SelectLevelPack(String levelPackId)
        {
            Logger.Trace("SelectLevelPack({0})", levelPackId);

            try
            {
                //var levelPacks = GetLevelPackCollection();
                IBeatmapLevelPack pack = GetLevelPackByPackId(levelPackId);

                if (pack == null)
                {
                    Logger.Debug("Could not locate requested level pack...");
                    return;
                }

                Logger.Info("Selecting level pack: {0}", pack.packID);

                LevelFilteringNavigationController.SelectBeatmapLevelPackOrPlayList(pack, null);
                LevelFilteringNavigationController.TabBarDidSwitch();

                Logger.Debug("Done selecting level pack!");
            }
            catch (Exception e)
            {
                Logger.Exception(e);
            }
        }
Exemplo n.º 2
0
 /// <summary>
 /// Sorting returns list sorted by alphabetical order of the directories they are contained in.
 /// </summary>
 /// <param name="levels"></param>
 /// <returns></returns>
 private List <IPreviewBeatmapLevel> SortVanilla(List <IPreviewBeatmapLevel> levels)
 {
     Logger.Info("Sorting song list by vanilla ordering.");
     return(levels
            .OrderBy(level => _cachedFileSystemOrder.TryGetValue(level.levelID, out int index) ? index : int.MaxValue)
            .ToList());
 }
        /// <summary>
        /// Select a level collection.
        /// </summary>
        /// <param name="levelCollectionName"></param>
        public void SelectLevelCollection(String levelCollectionName)
        {
            Logger.Trace("SelectLevelCollection({0})", levelCollectionName);

            try
            {
                IAnnotatedBeatmapLevelCollection collection = GetLevelCollectionByName(levelCollectionName);

                if (collection == null)
                {
                    Logger.Debug("Could not locate requested level collection...");
                    return;
                }

                Logger.Info("Selecting level collection: {0}", collection.collectionName);

                LevelFilteringNavigationController.SelectBeatmapLevelPackOrPlayList(collection as IBeatmapLevelPack, collection as IPlaylist);
                LevelFilteringNavigationController.TabBarDidSwitch();

                Logger.Debug("Done selecting level collection!");
            }
            catch (Exception e)
            {
                Logger.Exception(e);
            }
        }
 /// <summary>
 /// Sorting by newest (file time, creation+modified).
 /// </summary>
 /// <param name="levels"></param>
 /// <returns></returns>
 private List <IPreviewBeatmapLevel> SortNewest(List <IPreviewBeatmapLevel> levels)
 {
     Logger.Info("Sorting song list as newest.");
     return(levels
            .OrderByDescending(x => _cachedLastWriteTimes.ContainsKey(x.levelID) ? _cachedLastWriteTimes[x.levelID] : int.MinValue)
            .ToList());
 }
        /// <summary>
        /// Migrate old favorites into new system.
        /// </summary>
        public static void MigrateFavorites()
        {
            String migratedPlaylistPath = Path.Combine(Environment.CurrentDirectory, "Playlists", MigratedFavoritesPlaylistName);
            String oldFavoritesPath     = Path.Combine(Environment.CurrentDirectory, "Playlists", DefaultConvertedFavoritesPlaylistName);

            // Skip if already migrated or if the song browser favorites do not exist
            if (!File.Exists(oldFavoritesPath) || File.Exists(migratedPlaylistPath))
            {
                return;
            }

            Logger.Info("Migrating [{0}] into the In-Game favorites.", oldFavoritesPath);

            Playlist        oldFavorites = Playlist.LoadPlaylist(oldFavoritesPath);
            PlayerDataModel playerData   = Resources.FindObjectsOfTypeAll <PlayerDataModel>().FirstOrDefault();

            foreach (PlaylistSong song in oldFavorites.songs)
            {
                string levelID = CustomLevelLoader.kCustomLevelPrefixId + song.hash;
                Logger.Info("Migrating song into ingame favorites: {0}", levelID);
                playerData.playerData.favoritesLevelIds.Add(levelID);
            }

            Logger.Info("Moving [{0}->{1}] into the In-Game favorites.", oldFavoritesPath, migratedPlaylistPath);
            File.Move(oldFavoritesPath, migratedPlaylistPath);

            playerData.Save();
        }
#pragma warning disable IDE0051 // Remove unused private members
        static void Prefix(FlowCoordinator flowCoordinator, Action finishedCallback = null, ViewController.AnimationDirection animationDirection = ViewController.AnimationDirection.Horizontal, bool immediately = false, bool replaceTopViewController = false)
#pragma warning restore IDE0051 // Remove unused private members
        {
            var flowType = flowCoordinator.GetType();

            if (flowType == typeof(SoloFreePlayFlowCoordinator))
            {
                Logger.Info("Initializing SongBrowser for Single Player Mode");
                SongBrowser.SongBrowserApplication.Instance.HandleSoloModeSelection();
            }
            else if (flowType == typeof(MultiplayerLevelSelectionFlowCoordinator))
            {
                Logger.Info("Initializing SongBrowser for Multiplayer Mode");
                SongBrowser.SongBrowserApplication.Instance.HandleMultiplayerModeSelection();
            }
            else if (flowType == typeof(PartyFreePlayFlowCoordinator))
            {
                Logger.Info("Initializing SongBrowser for Party Mode");
                SongBrowser.SongBrowserApplication.Instance.HandlePartyModeSelection();
            }
            else if (flowType == typeof(CampaignFlowCoordinator))
            {
                Logger.Info("Initializing SongBrowser for Multiplayer Mode");
                SongBrowser.SongBrowserApplication.Instance.HandleCampaignModeSelection();
            }

            return;
        }
        /// <summary>
        /// Save helper.
        /// </summary>
        private void _Save(String path)
        {
            if (this.DisableSavingSettings)
            {
                Logger.Info("Saving settings has been disabled...");
                return;
            }

            if (searchTerms.Count > 10)
            {
                searchTerms.RemoveRange(10, searchTerms.Count - 10);
            }

            var settings = new XmlWriterSettings
            {
                OmitXmlDeclaration  = false,
                Indent              = true,
                NewLineOnAttributes = true,
                NewLineHandling     = System.Xml.NewLineHandling.Entitize
            };

            using (var stream = new StreamWriter(path, false, Utf8Encoding))
            {
                using (var writer = XmlWriter.Create(stream, settings))
                {
                    SettingsSerializer.Serialize(writer, this);
                }
            }
        }
        /// <summary>
        /// Select a level pack.
        /// </summary>
        /// <param name="levelPackId"></param>
        public void SelectLevelPack(String levelPackId)
        {
            Logger.Trace("SelectLevelPack({0})", levelPackId);

            try
            {
                var levelPacks = GetLevelPackCollection();
                var index      = GetLevelPackIndexByPackId(levelPackId);
                var pack       = GetLevelPackByPackId(levelPackId);

                if (index < 0)
                {
                    Logger.Debug("Cannot select level packs yet...");
                    return;
                }

                Logger.Info("Selecting level pack index: {0}", pack.packName);
                var tableView = LevelPacksTableView.GetPrivateField <TableView>("_tableView");

                LevelPacksTableView.SelectCellWithIdx(index);
                tableView.SelectCellWithIdx(index, true);
                tableView.ScrollToCellWithIdx(0, TableViewScroller.ScrollPositionType.Beginning, false);
                for (int i = 0; i < index; i++)
                {
                    tableView.GetPrivateField <TableViewScroller>("_scroller").PageScrollDown();
                }

                Logger.Debug("Done selecting level pack!");
            }
            catch (Exception e)
            {
                Logger.Exception(e);
            }
        }
Exemplo n.º 9
0
        /// <summary>
        /// Sorting by star rating.
        /// </summary>
        /// <param name="levels"></param>
        /// <returns></returns>
        private List <IPreviewBeatmapLevel> SortStars(List <IPreviewBeatmapLevel> levels)
        {
            Logger.Info("Sorting song list by star points...");

            if (!SongDataCore.Plugin.Songs.IsDataAvailable())
            {
                SortWasMissingData = true;
                return(levels);
            }

            return(levels
                   .OrderByDescending(x =>
            {
                var hash = SongBrowserModel.GetSongHash(x.levelID);
                var stars = 0.0;
                if (SongDataCore.Plugin.Songs.Data.Songs.ContainsKey(hash))
                {
                    var diffs = SongDataCore.Plugin.Songs.Data.Songs[hash].diffs;
                    stars = diffs.Max(y => (PluginConfig.Instance.InvertSortResults)
                            ? -y.star
                            : y.star);
                }

                //Logger.Debug("Stars={0}", stars);
                if (stars != 0)
                {
                    return stars;
                }

                return double.MinValue;
            })
                   .ToList());
        }
Exemplo n.º 10
0
        private List <BeatmapLevelSO> FilterSearch(List <BeatmapLevelSO> levels)
        {
            // Make sure we can actually search.
            if (this._settings.searchTerms.Count <= 0)
            {
                Logger.Error("Tried to search for a song with no valid search terms...");
                SortSongName(levels);
                return(levels);
            }
            string searchTerm = this._settings.searchTerms[0];

            if (String.IsNullOrEmpty(searchTerm))
            {
                Logger.Error("Empty search term entered.");
                SortSongName(levels);
                return(levels);
            }

            Logger.Info("Filtering song list by search term: {0}", searchTerm);

            var terms = searchTerm.Split(' ');

            foreach (var term in terms)
            {
                levels = levels.Intersect(
                    levels
                    .Where(x => $"{x.songName} {x.songSubName} {x.songAuthorName}".ToLower().Contains(term.ToLower()))
                    .ToList(
                        )
                    ).ToList();
            }

            return(levels);
        }
Exemplo n.º 11
0
        private void SortDifficulty(List <BeatmapLevelSO> levels)
        {
            Logger.Info("Sorting song list by difficulty...");

            IEnumerable <BeatmapDifficulty> difficultyIterator       = Enum.GetValues(typeof(BeatmapDifficulty)).Cast <BeatmapDifficulty>();
            Dictionary <string, int>        levelIdToDifficultyValue = new Dictionary <string, int>();

            foreach (var level in levels)
            {
                if (!levelIdToDifficultyValue.ContainsKey(level.levelID))
                {
                    int difficultyValue = 0;

                    // Get the beatmap difficulties
                    var difficulties = level.difficultyBeatmapSets
                                       .Where(x => x.beatmapCharacteristic == this.CurrentBeatmapCharacteristicSO)
                                       .SelectMany(x => x.difficultyBeatmaps);

                    foreach (IDifficultyBeatmap difficultyBeatmap in difficulties)
                    {
                        difficultyValue += _difficultyWeights[difficultyBeatmap.difficulty];
                    }
                    levelIdToDifficultyValue.Add(level.levelID, difficultyValue);
                }
            }

            _sortedSongs = levels
                           .OrderBy(x => levelIdToDifficultyValue[x.levelID])
                           .ThenBy(x => x.songName)
                           .ToList();
            _sortedSongs = levels;
        }
        /// <summary>
        /// Sorting by BeatSaver heat stat.
        /// </summary>
        /// <param name="levelIds"></param>
        /// <returns></returns>
        private List <IPreviewBeatmapLevel> SortBeatSaverHeat(List <IPreviewBeatmapLevel> levelIds)
        {
            Logger.Info("Sorting song list by BeatSaver Heat!");

            // Do not always have data when trying to sort by heat
            if (!SongDataCore.Plugin.Songs.IsDataAvailable())
            {
                SortWasMissingData = true;
                return(levelIds);
            }

            return(levelIds
                   .OrderByDescending(x => {
                var hash = SongBrowserModel.GetSongHash(x.levelID);
                if (SongDataCore.Plugin.Songs.Data.Songs.ContainsKey(hash))
                {
                    return SongDataCore.Plugin.Songs.Data.Songs[hash].heat;
                }
                else
                {
                    return int.MinValue;
                }
            })
                   .ToList());
        }
        /// <summary>
        /// Sorting by BeatSaver playcount stat.
        /// </summary>
        /// <param name="levelIds"></param>
        /// <returns></returns>
        private List <IPreviewBeatmapLevel> SortBeatSaverPlayCount(List <IPreviewBeatmapLevel> levelIds)
        {
            Logger.Info("Sorting song list by BeatSaver PlayCount");
            return(levelIds);
            // Do not always have data when trying to sort by UpVotes

            /*if (!SongDataCore.Plugin.Songs.IsDataAvailable())
             * {
             *  SortWasMissingData = true;
             *  return levelIds;
             * }
             *
             * return levelIds
             *  .OrderByDescending(x => {
             *      var hash = SongBrowserModel.GetSongHash(x.levelID);
             *      if (SongDataCore.Plugin.Songs.Data.Songs.ContainsKey(hash))
             *      {
             *          return SongDataCore.Plugin.Songs.Data.Songs[hash].plays;
             *      }
             *      else
             *      {
             *          return int.MinValue;
             *      }
             *  })
             *  .ToList();*/
        }
        /// <summary>
        /// Sorting by PP.
        /// </summary>
        /// <param name="levels"></param>
        /// <returns></returns>
        private List <IPreviewBeatmapLevel> SortPerformancePoints(List <IPreviewBeatmapLevel> levels)
        {
            Logger.Info("Sorting song list by performance points...");

            if (!SongDataCore.Plugin.Songs.IsDataAvailable())
            {
                SortWasMissingData = true;
                return(levels);
            }

            return(levels
                   .OrderByDescending(x =>
            {
                var hash = SongBrowserModel.GetSongHash(x.levelID);
                if (SongDataCore.Plugin.Songs.Data.Songs.ContainsKey(hash))
                {
                    return SongDataCore.Plugin.Songs.Data.Songs[hash].diffs.Max(y => y.pp);
                }
                else
                {
                    return 0;
                }
            })
                   .ToList());
        }
 /// <summary>
 /// Sorting by the song name.
 /// </summary>
 /// <param name="levels"></param>
 /// <returns></returns>
 private List <IPreviewBeatmapLevel> SortSongLength(List <IPreviewBeatmapLevel> levels)
 {
     Logger.Info("Sorting song list by songDuration/songName");
     return(levels
            .OrderBy(x => x.songDuration)
            .ThenBy(x => x.songName)
            .ToList());
 }
 /// <summary>
 /// Sorting by the song name.
 /// </summary>
 /// <param name="levels"></param>
 /// <returns></returns>
 private List <IPreviewBeatmapLevel> SortSongName(List <IPreviewBeatmapLevel> levels)
 {
     Logger.Info("Sorting song list as default (songName)");
     return(levels
            .OrderBy(x => x.songName)
            .ThenBy(x => x.songAuthorName)
            .ToList());
 }
 /// <summary>
 /// Sorting by song users play count.
 /// </summary>
 /// <param name="levels"></param>
 /// <returns></returns>
 private List <IPreviewBeatmapLevel> SortPlayCount(List <IPreviewBeatmapLevel> levels)
 {
     Logger.Info("Sorting song list by playcount");
     return(levels
            .OrderByDescending(x => _levelIdToPlayCount.ContainsKey(x.levelID) ? _levelIdToPlayCount[x.levelID] : 0)
            .ThenBy(x => x.songName)
            .ToList());
 }
 /// <summary>
 /// Sorting by the song author.
 /// </summary>
 /// <param name="levelIds"></param>
 /// <returns></returns>
 private List <IPreviewBeatmapLevel> SortAuthor(List <IPreviewBeatmapLevel> levelIds)
 {
     Logger.Info("Sorting song list by author");
     return(levelIds
            .OrderBy(x => x.songAuthorName)
            .ThenBy(x => x.songName)
            .ToList());
 }
        /// <summary>
        /// Filter songs based on playerdata favorites.
        /// </summary>
        private List <IPreviewBeatmapLevel> FilterFavorites(List <IPreviewBeatmapLevel> levels)
        {
            Logger.Info("Filtering song list as favorites playlist...");

            PlayerDataModel playerData = Resources.FindObjectsOfTypeAll <PlayerDataModel>().FirstOrDefault();

            return(levels.Where(x => playerData.playerData.favoritesLevelIds.Contains(x.levelID)).ToList());
        }
 /// <summary>
 /// Sorting by the song name.
 /// </summary>
 /// <param name="levels"></param>
 /// <returns></returns>
 private List <IPreviewBeatmapLevel> SortSongBpm(List <IPreviewBeatmapLevel> levels)
 {
     Logger.Info("Sorting song list by beatsPerMinute/songName");
     return(levels
            .OrderBy(x => x.beatsPerMinute)
            .ThenBy(x => x.songName)
            .ToList());
 }
Exemplo n.º 21
0
 private void SortSongName(List <BeatmapLevelSO> levels)
 {
     Logger.Info("Sorting song list as default (songName)");
     _sortedSongs = levels
                    .OrderBy(x => x.songName)
                    .ThenBy(x => x.songAuthorName)
                    .ToList();
 }
Exemplo n.º 22
0
        private void SortPerformancePoints(List <BeatmapLevelSO> levels)
        {
            Logger.Info("Sorting song list by performance points...");

            _sortedSongs = levels
                           .OrderByDescending(x => _levelIdToScoreSaberData.ContainsKey(x.levelID) ? _levelIdToScoreSaberData[x.levelID].maxPp : 0)
                           .ToList();
        }
Exemplo n.º 23
0
 private void SortPlayCount(List <BeatmapLevelSO> levels)
 {
     Logger.Info("Sorting song list by playcount");
     _sortedSongs = levels
                    .OrderByDescending(x => _levelIdToPlayCount[x.levelID])
                    .ThenBy(x => x.songName)
                    .ToList();
 }
Exemplo n.º 24
0
 private void SortNewest(List <BeatmapLevelSO> levels)
 {
     Logger.Info("Sorting song list as newest.");
     _sortedSongs = levels
                    .OrderBy(x => _weights.ContainsKey(x.levelID) ? _weights[x.levelID] : 0)
                    .ThenByDescending(x => !_levelIdToCustomLevel.ContainsKey(x.levelID) ? (_weights.ContainsKey(x.levelID) ? _weights[x.levelID] : 0) : _cachedLastWriteTimes[x.levelID])
                    .ToList();
 }
Exemplo n.º 25
0
 /// <summary>
 /// For now the editing playlist will be considered the favorites playlist.
 /// Users can edit the settings file themselves.
 /// </summary>
 private List <BeatmapLevelSO> FilterFavorites()
 {
     Logger.Info("Filtering song list as favorites playlist...");
     if (this.CurrentEditingPlaylist != null)
     {
         this.CurrentPlaylist = this.CurrentEditingPlaylist;
     }
     return(this.FilterPlaylist());
 }
        /// <summary>
        /// Get the song cache from the game.
        /// </summary>
        public void UpdateLevelRecords()
        {
            Stopwatch timer = new Stopwatch();

            timer.Start();

            // Calculate some information about the custom song dir
            String customSongsPath                   = Path.Combine(Environment.CurrentDirectory, CUSTOM_SONGS_DIR);
            String revSlashCustomSongPath            = customSongsPath.Replace('\\', '/');
            double currentCustomSongDirLastWriteTIme = (File.GetLastWriteTimeUtc(customSongsPath) - EPOCH).TotalMilliseconds;
            bool   customSongDirChanged              = false;

            if (_customSongDirLastWriteTime != currentCustomSongDirLastWriteTIme)
            {
                customSongDirChanged        = true;
                _customSongDirLastWriteTime = currentCustomSongDirLastWriteTIme;
            }

            if (!Directory.Exists(customSongsPath))
            {
                Logger.Error("CustomSong directory is missing...");
                return;
            }

            // Map some data for custom songs
            Regex     r = new Regex(@"(\d+-\d+)", RegexOptions.IgnoreCase);
            Stopwatch lastWriteTimer = new Stopwatch();

            lastWriteTimer.Start();
            foreach (KeyValuePair <string, CustomPreviewBeatmapLevel> level in SongCore.Loader.CustomLevels)
            {
                // If we already know this levelID, don't both updating it.
                // SongLoader should filter duplicates but in case of failure we don't want to crash
                if (!_cachedLastWriteTimes.ContainsKey(level.Value.levelID) || customSongDirChanged)
                {
                    double lastWriteTime = GetSongUserDate(level.Value);
                    _cachedLastWriteTimes[level.Value.levelID] = lastWriteTime;
                }
            }

            lastWriteTimer.Stop();
            Logger.Info("Determining song download time and determining mappings took {0}ms", lastWriteTimer.ElapsedMilliseconds);

            // Update song Infos, directory tree, and sort
            this.UpdatePlayCounts();

            // Signal complete
            if (SongCore.Loader.CustomLevels.Count > 0)
            {
                didFinishProcessingSongs?.Invoke(SongCore.Loader.CustomLevels);
            }

            timer.Stop();

            Logger.Info("Updating songs infos took {0}ms", timer.ElapsedMilliseconds);
        }
Exemplo n.º 27
0
        private void SortRandom(List <BeatmapLevelSO> levels)
        {
            Logger.Info("Sorting song list by random (seed={0})...", this.Settings.randomSongSeed);

            System.Random rnd = new System.Random(this.Settings.randomSongSeed);

            _sortedSongs = levels
                           .OrderBy(x => rnd.Next())
                           .ToList();
        }
        /// <summary>
        /// Randomize the sorting.
        /// </summary>
        /// <param name="levelIds"></param>
        /// <returns></returns>
        private List <IPreviewBeatmapLevel> SortRandom(List <IPreviewBeatmapLevel> levelIds)
        {
            Logger.Info("Sorting song list by random (seed={0})...", Settings.randomSongSeed);

            System.Random rnd = new System.Random(Settings.randomSongSeed);

            return(levelIds
                   .OrderBy(x => x.songName)
                   .OrderBy(x => rnd.Next())
                   .ToList());
        }
Exemplo n.º 29
0
        public bool SelectLevelCategory(String levelCategoryName)
        {
            Logger.Trace("SelectLevelCategory({0})", levelCategoryName);

            try
            {
                if (String.IsNullOrEmpty(levelCategoryName))
                {
                    // hack for now, just assume custom levels if a user has an old settings file, corrects itself first time they change level packs.
                    levelCategoryName = SelectLevelCategoryViewController.LevelCategory.CustomSongs.ToString();
                }

                SelectLevelCategoryViewController.LevelCategory category;
                try
                {
                    category = (SelectLevelCategoryViewController.LevelCategory)Enum.Parse(typeof(SelectLevelCategoryViewController.LevelCategory), levelCategoryName, true);
                }
                catch (Exception)
                {
                    // invalid input
                    return(false);
                }

                if (category == LevelFilteringNavigationController.selectedLevelCategory)
                {
                    Logger.Debug($"Level category [{category}] is already selected");
                    return(false);
                }

                Logger.Info("Selecting level category: {0}", levelCategoryName);

                var selectLeveCategoryViewController = LevelFilteringNavigationController.GetComponentInChildren <SelectLevelCategoryViewController>();
                var iconSegementController           = selectLeveCategoryViewController.GetComponentInChildren <IconSegmentedControl>();

                int selectCellNumber = (from x in selectLeveCategoryViewController.GetField <SelectLevelCategoryViewController.LevelCategoryInfo[], SelectLevelCategoryViewController>("_levelCategoryInfos")
                                        select x.levelCategory).ToList().IndexOf(category);

                iconSegementController.SelectCellWithNumber(selectCellNumber);
                selectLeveCategoryViewController.LevelFilterCategoryIconSegmentedControlDidSelectCell(iconSegementController, selectCellNumber);
                LevelFilteringNavigationController.UpdateSecondChildControllerContent(category);
                //AnnotatedBeatmapLevelCollectionsViewController.RefreshAvailability();

                Logger.Debug("Done selecting level category.");

                return(true);
            } catch (Exception e)
            {
                Logger.Exception(e);
            }

            return(false);
        }
        /// <summary>
        /// Scroll TableView to proper row, fire events.
        /// </summary>
        /// <param name="table"></param>
        /// <param name="levelID"></param>
        public void SelectAndScrollToLevel(string levelID)
        {
            Logger.Debug("Scrolling to LevelID: {0}", levelID);

            // Check once per load
            if (!_checkedForTwitchPlugin)
            {
                Logger.Info("Checking for BeatSaber Twitch Integration Plugin...");
                _detectedTwitchPluginQueue = Resources.FindObjectsOfTypeAll <HMUI.ViewController>().Any(x => x.name == "RequestInfo");
                Logger.Info("BeatSaber Twitch Integration plugin detected: " + _detectedTwitchPluginQueue);

                _checkedForTwitchPlugin = true;
            }

            // Skip scrolling to level if twitch plugin has queue active.
            if (_detectedTwitchPluginQueue)
            {
                Logger.Debug("Skipping SelectAndScrollToLevel() because we detected Twitch Integration Plugin has a Queue active...");
                return;
            }

            // try to find the index and scroll to it
            int selectedIndex = 0;
            List <IPreviewBeatmapLevel> levels = GetCurrentLevelCollectionLevels().ToList();

            if (levels.Count <= 0)
            {
                return;
            }

            // acquire the index or try the last row
            selectedIndex = levels.FindIndex(x => x.levelID == levelID);
            if (selectedIndex < 0)
            {
                // this might look like an off by one error but the _level list we keep is missing the header entry BeatSaber.
                // so the last row is +1 the max index, the count.
                int maxCount = levels.Count;

                int selectedRow = LevelCollectionTableView.GetPrivateField <int>("_selectedRow");

                Logger.Debug("Song is not in the level pack, cannot scroll to it...  Using last known row {0}/{1}", selectedRow, maxCount);
                selectedIndex = Math.Min(maxCount, selectedRow);
            }
            else
            {
                // the header counts as an index, so if the index came from the level array we have to add 1.
                selectedIndex += 1;
            }

            ScrollToLevelByRow(selectedIndex);
        }