/// <summary> /// Filter for a search query. /// </summary> /// <param name="levels"></param> /// <returns></returns> private List <IPreviewBeatmapLevel> FilterSearch(List <IPreviewBeatmapLevel> 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 => { var hash = SongBrowserModel.GetSongHash(x.levelID); var songKey = ""; if (SongDataCore.Plugin.Songs.Data.Songs.ContainsKey(hash)) { songKey = SongDataCore.Plugin.Songs.Data.Songs[hash].key; } return($"{songKey} {x.songName} {x.songSubName} {x.songAuthorName} {x.levelAuthorName}".ToLower().Contains(term.ToLower())); }) .ToList( ) ).ToList(); } return(levels); }
/// <summary> /// Update mapping of scrapped song data. /// </summary> private void OnBeatSaverDataDownloaded() { Logger.Trace("OnBeatSaverDataDownloaded"); try { if (_songBrowserModel.Settings.sortMode.NeedsBeatSaverData()) { _songBrowserUI.ProcessSongList(); _songBrowserUI.RefreshSongUI(); } else { _songBrowserUI.RefreshSortButtonUI(); } } catch (Exception e) { Logger.Exception("Exception during OnDownloaderScrappedDataDownloaded: ", e); } }
public void Refresh() { int removed = queuedSongs.RemoveAll(x => x.songQueueState == SongQueueState.Downloaded || x.songQueueState == SongQueueState.Error); Logger.Log($"Removed {removed} songs from queue"); _queuedSongsTableView.ReloadData(); _queuedSongsTableView.ScrollToCellWithIdx(0, TableViewScroller.ScrollPositionType.Beginning, true); if (queuedSongs.Count(x => x.songQueueState == SongQueueState.Downloading || x.songQueueState == SongQueueState.Queued) == 0) { Logger.Log("All songs downloaded!"); allSongsDownloaded?.Invoke(); } if (queuedSongs.Count(x => x.songQueueState == SongQueueState.Downloading) < PluginConfig.maxSimultaneousDownloads && queuedSongs.Any(x => x.songQueueState == SongQueueState.Queued)) { StartCoroutine(DownloadSong(queuedSongs.First(x => x.songQueueState == SongQueueState.Queued))); } }
/// <summary> /// Get a copy of the unfiltered, unsorted list of songs from level packs. /// </summary> public void UpdateLevelPackOriginalLists() { BeatmapLevelPackSO[] levelPacks = Resources.FindObjectsOfTypeAll <BeatmapLevelPackSO>(); foreach (BeatmapLevelPackSO levelPack in levelPacks) { Logger.Debug("Attempting to get song list from levelPack: {0}...", levelPack); var beatmapLevelPack = levelPack as BeatmapLevelPackSO; // TODO - need to rethink interface here, not all level packs can be cast this high, some sort functions need it. // - this helps prevent DLC from breaking everything if (beatmapLevelPack == null) { continue; } _levelPackToSongs[levelPack.packName] = (beatmapLevelPack.beatmapLevelCollection as BeatmapLevelCollectionSO).GetPrivateField <BeatmapLevelSO[]>("_beatmapLevels").ToList(); Logger.Debug("Got {0} songs from level collections...", _levelPackToSongs[levelPack.packName].Count); //_levelPackToSongs[levelPack.packName].ForEach(x => Logger.Debug("{0} by {1} = {2}", x.name, x.levelAuthorName, x.levelID)); } }
/// <summary> /// Attempt to sort by songs containing easy first /// </summary> /// <param name="levels"></param> /// <returns></returns> private List <IPreviewBeatmapLevel> SortDifficulty(List <IPreviewBeatmapLevel> levels) { Logger.Info("Sorting song list by difficulty (DISABLED!!!)..."); /* * IEnumerable<BeatmapDifficulty> difficultyIterator = Enum.GetValues(typeof(BeatmapDifficulty)).Cast<BeatmapDifficulty>(); * Dictionary<string, int> levelIdToDifficultyValue = new Dictionary<string, int>(); * foreach (IPreviewBeatmapLevel level in levels) * { * // only need to process a level once * if (levelIdToDifficultyValue.ContainsKey(level.levelID)) * { * continue; * } * * // TODO - fix, not honoring beatmap characteristic. * int difficultyValue = 0; * if (level as BeatmapLevelSO != null) * { * var beatmapSet = (level as BeatmapLevelSO).difficultyBeatmapSets; * difficultyValue = beatmapSet * .SelectMany(x => x.difficultyBeatmaps) * .Sum(x => _difficultyWeights[x.difficulty]); * } * else if (_levelIdToCustomLevel.ContainsKey(level.levelID)) * { * var beatmapSet = (_levelIdToCustomLevel[level.levelID] as CustomPreviewBeatmapLevel).standardLevelInfoSaveData.difficultyBeatmapSets; * difficultyValue = beatmapSet * .SelectMany(x => x.difficultyBeatmaps) * .Sum(x => _difficultyWeights[(BeatmapDifficulty)Enum.Parse(typeof(BeatmapDifficulty), x.difficulty)]); * } * * levelIdToDifficultyValue.Add(level.levelID, difficultyValue); * } * * return levels * .OrderBy(x => levelIdToDifficultyValue[x.levelID]) * .ThenBy(x => x.songName) * .ToList();*/ return(levels); }
public IEnumerator RequestSongByKeyCoroutine(string key, Action <Song> callback) { UnityWebRequest wwwId = UnityWebRequest.Get($"{PluginConfig.beatsaverURL}/api/maps/detail/" + key); wwwId.timeout = 10; yield return(wwwId.SendWebRequest()); if (wwwId.isNetworkError || wwwId.isHttpError) { Logger.Error(wwwId.error); } else { JObject node = JObject.Parse(wwwId.downloadHandler.text); Song _tempSong = new Song((JObject)node, false); callback?.Invoke(_tempSong); } }
/// <summary> /// Parse the current pp data file. /// Public so controllers can decide when to update it. /// </summary> public void UpdateScoreSaberDataMapping() { Logger.Trace("UpdateScoreSaberDataMapping()"); ScoreSaberDataFile scoreSaberDataFile = ScoreSaberDatabaseDownloader.ScoreSaberDataFile; // bail if (scoreSaberDataFile == null) { Logger.Warning("Score saber data is not ready yet..."); return; } foreach (var level in SongLoader.CustomLevels) { // Skip if (_levelIdToScoreSaberData.ContainsKey(level.levelID)) { continue; } ScoreSaberData scoreSaberData = null; // try to version match first if (_levelIdToSongVersion.ContainsKey(level.levelID)) { String version = _levelIdToSongVersion[level.levelID]; if (scoreSaberDataFile.SongVersionToScoreSaberData.ContainsKey(version)) { scoreSaberData = scoreSaberDataFile.SongVersionToScoreSaberData[version]; } } if (scoreSaberData != null) { //Logger.Debug("{0} = {1}pp", level.songName, pp); _levelIdToScoreSaberData.Add(level.levelID, scoreSaberData); } } }
public IEnumerator DownloadPlaylist(Playlist playlist) { PlaylistsCollection.MatchSongsForPlaylist(playlist, true); List <PlaylistSong> needToDownload = playlist.songs.Where(x => x.level == null).ToList(); Logger.Log($"Need to download {needToDownload.Count} songs"); _downloadingPlaylist = true; foreach (var item in needToDownload) { Song beatSaverSong = null; if (String.IsNullOrEmpty(playlist.customArchiveUrl)) { Logger.Log("Obtaining hash and url for " + item.key + ": " + item.songName); yield return(GetInfoForSong(playlist, item, (Song song) => { beatSaverSong = song; })); } else { string archiveUrl = playlist.customArchiveUrl.Replace("[KEY]", item.key); beatSaverSong = new Song() { songName = item.songName, id = item.key, downloadingProgress = 0f, hash = (item.levelId == null ? "" : item.levelId), downloadUrl = archiveUrl }; } if (beatSaverSong != null && !SongLoader.CustomLevels.Any(x => x.levelID.Substring(0, 32) == beatSaverSong.hash.ToUpper())) { _downloadQueueViewController.EnqueueSong(beatSaverSong, true); } } _downloadingPlaylist = false; }
/// <summary> /// Wait for score saber related files to download. /// </summary> /// <returns></returns> private IEnumerator WaitForDownload() { if (ScoreSaberDatabaseDownloader.ScoreSaberDataFile != null) { Logger.Info("Using cached copy of ScoreSaberData..."); } else { SongBrowserApplication.MainProgressBar.ShowMessage("Downloading BeatStar data...", 5.0f); Logger.Info("Attempting to download: {0}", ScoreSaberDatabaseDownloader.SCRAPED_SCORE_SABER_JSON_URL); using (UnityWebRequest www = UnityWebRequest.Get(ScoreSaberDatabaseDownloader.SCRAPED_SCORE_SABER_JSON_URL)) { // Use 4MB cache, large enough for this file to grow for awhile. www.SetCacheable(new CacheableDownloadHandlerScoreSaberData(www, _buffer)); yield return(www.SendWebRequest()); Logger.Debug("Returned from web request!..."); try { ScoreSaberDatabaseDownloader.ScoreSaberDataFile = (www.downloadHandler as CacheableDownloadHandlerScoreSaberData).ScoreSaberDataFile; Logger.Info("Success downloading ScoreSaber data!"); SongBrowserApplication.MainProgressBar.ShowMessage("Success downloading BeatStar data...", 5.0f); onScoreSaberDataDownloaded?.Invoke(); } catch (System.InvalidOperationException) { Logger.Error("Failed to download ScoreSaber data file..."); } catch (Exception e) { Logger.Exception("Exception trying to download ScoreSaber data file...", e); } } } }
/// <summary> /// Try to refresh the song list. Broken for now. /// </summary> public void RefreshSongList(string currentSelectedLevelId, bool scrollToLevel = true) { Logger.Info("Refreshing the song list view."); try { var levels = GetCurrentLevelPackLevels(); Logger.Debug("Checking if TableView is initialized..."); TableView tableView = ReflectionUtil.GetPrivateField <TableView>(LevelPackLevelsTableView, "_tableView"); bool tableViewInit = ReflectionUtil.GetPrivateField <bool>(tableView, "_isInitialized"); Logger.Debug("Reloading SongList TableView"); tableView.ReloadData(); Logger.Debug("Attempting to scroll to level..."); String selectedLevelID = currentSelectedLevelId; if (!String.IsNullOrEmpty(currentSelectedLevelId)) { selectedLevelID = currentSelectedLevelId; } else { if (levels.Length > 0) { selectedLevelID = levels.FirstOrDefault().levelID; } } if (scrollToLevel) { SelectAndScrollToLevel(LevelPackLevelsTableView, selectedLevelID); } } catch (Exception e) { Logger.Exception("Exception refreshing song list:", e); } }
/// <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 => y.star); } //Logger.Debug("Stars={0}", stars); if (stars != 0) { return stars; } if (_settings.invertSortResults) { return double.MaxValue; } else { return double.MinValue; } }) .ToList()); }
/// <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.BeatSaver.IsDataAvailable()) { return(levelIds); } return(levelIds .OrderByDescending(x => { var hash = CustomHelpers.GetSongHash(x.levelID); if (SongDataCore.Plugin.BeatSaver.Data.Songs.ContainsKey(hash)) { return SongDataCore.Plugin.BeatSaver.Data.Songs[hash].stats.heat; } else { return int.MinValue; } }) .ToList()); }
/// <summary> /// Set current level pack, reset all packs just in case. /// </summary> /// <param name="pack"></param> public void SetCurrentLevelPack(IBeatmapLevelPack pack) { Logger.Debug("Setting current level pack [{0}]: {1}", pack.packID, pack.packName); this.ResetLevelPacks(); this._currentLevelPack = pack; var beatmapLevelPack = pack as BeatmapLevelPackSO; if (beatmapLevelPack == null) { Logger.Debug("DLC Detected... Disabling SongBrowser..."); _isPreviewLevelPack = true; } else { Logger.Debug("Owned level pack... Enabling SongBrowser..."); _isPreviewLevelPack = false; } this.Settings.currentLevelPackId = pack.packID; this.Settings.Save(); }
public IEnumerator RequestSongByKeyCoroutine(string key, Action <Song> callback) { UnityWebRequest wwwId = UnityWebRequest.Get($"{PluginConfig.beatsaverURL}/api/songs/detail/" + key); wwwId.timeout = 10; yield return(wwwId.SendWebRequest()); if (wwwId.isNetworkError || wwwId.isHttpError) { Logger.Error(wwwId.error); } else { #if DEBUG Logger.Log("Received response from BeatSaver..."); #endif JSONNode node = JSON.Parse(wwwId.downloadHandler.text); Song _tempSong = new Song(node["song"]); callback?.Invoke(_tempSong); } }
public static Button CreatePageButton(String name, RectTransform parent, String buttonTemplate, Vector2 anchoredPosition, Vector2 sizeDelta, UnityAction onClick, Sprite icon) { Logger.Debug("CreatePageButton({0}, {1}, {2}, {3}, {4}", name, parent, buttonTemplate, anchoredPosition, sizeDelta); Button btn = CreateBaseButton(name, parent, buttonTemplate); (btn.transform as RectTransform).anchorMin = new Vector2(0.5f, 0.5f); (btn.transform as RectTransform).anchorMax = new Vector2(0.5f, 0.5f); (btn.transform as RectTransform).anchoredPosition = anchoredPosition; (btn.transform as RectTransform).sizeDelta = sizeDelta; (btn.transform as RectTransform).pivot = new Vector2(0.5f, 0.5f); ButtonIconImage btnIcon = btn.gameObject.AddComponent <ButtonIconImage>(); btnIcon.image = btn.gameObject.GetComponentsInChildren <Image>(true).Where(x => x.gameObject.name == "Icon").FirstOrDefault(); btnIcon.image.sprite = icon; btn.onClick.RemoveAllListeners(); if (onClick != null) { btn.onClick.AddListener(onClick); } return(btn); }
/// <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.SelectAnnotatedBeatmapLevelCollection(collection as IBeatmapLevelPack); Logger.Debug("Done selecting level collection!"); } catch (Exception e) { Logger.Exception(e); } }
/// <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.ScoreSaber.IsDataAvailable()) { return(levels); } return(levels .OrderByDescending(x => { var hash = CustomHelpers.GetSongHash(x.levelID); if (SongDataCore.Plugin.ScoreSaber.Data.Songs.ContainsKey(hash)) { return SongDataCore.Plugin.ScoreSaber.Data.Songs[hash].diffs.Max(y => y.pp); } else { return 0; } }) .ToList()); }
/// <summary> /// Handle Party Mode /// </summary> /// <param name="arg1"></param> /// <param name="arg2"></param> private void HandlePartyModeSelection() { Logger.Trace("HandlePartyModeSelection()"); HandleModeSelection(MainMenuViewController.MenuButton.Party); _songBrowserUI.Show(); }
/// <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; } IEnumerable <string> directories = Directory.EnumerateDirectories(customSongsPath, "*.*", SearchOption.AllDirectories); // Get LastWriteTimes Stopwatch lastWriteTimer = new Stopwatch(); lastWriteTimer.Start(); foreach (var level in SongLoader.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.levelID) || customSongDirChanged) { // Always use the newest date. var lastWriteTime = File.GetLastWriteTimeUtc(level.customSongInfo.path); var lastCreateTime = File.GetCreationTimeUtc(level.customSongInfo.path); var lastTime = lastWriteTime > lastCreateTime ? lastWriteTime : lastCreateTime; _cachedLastWriteTimes[level.levelID] = (lastTime - EPOCH).TotalMilliseconds; } if (!_levelIdToCustomLevel.ContainsKey(level.levelID)) { _levelIdToCustomLevel.Add(level.levelID, level); } if (!_levelIdToSongVersion.ContainsKey(level.levelID)) { DirectoryInfo info = new DirectoryInfo(level.customSongInfo.path); string currentDirectoryName = info.Name; String version = level.customSongInfo.path.Replace(revSlashCustomSongPath, "").Replace(currentDirectoryName, "").Replace("/", ""); if (!String.IsNullOrEmpty(version)) { _levelIdToSongVersion.Add(level.levelID, version); if (!_keyToSong.ContainsKey(version)) { _keyToSong.Add(version, level); } } } } lastWriteTimer.Stop(); Logger.Info("Determining song download time and determining mappings took {0}ms", lastWriteTimer.ElapsedMilliseconds); // Update song Infos, directory tree, and sort this.UpdateScoreSaberDataMapping(); this.UpdatePlayCounts(); // Check if we need to upgrade settings file favorites try { this.Settings.ConvertFavoritesToPlaylist(_levelIdToCustomLevel, _levelIdToSongVersion); } catch (Exception e) { Logger.Exception("FAILED TO CONVERT FAVORITES TO PLAYLIST!", e); } // load the current editing playlist or make one if (CurrentEditingPlaylist == null && !String.IsNullOrEmpty(this.Settings.currentEditingPlaylistFile)) { Logger.Debug("Loading playlist for editing: {0}", this.Settings.currentEditingPlaylistFile); CurrentEditingPlaylist = Playlist.LoadPlaylist(this.Settings.currentEditingPlaylistFile); PlaylistsCollection.MatchSongsForPlaylist(CurrentEditingPlaylist); } if (CurrentEditingPlaylist == null) { Logger.Debug("Current editing playlist does not exit, create..."); CurrentEditingPlaylist = new Playlist { playlistTitle = "Song Browser Favorites", playlistAuthor = "SongBrowser", fileLoc = this.Settings.currentEditingPlaylistFile, image = Base64Sprites.PlaylistIconB64, songs = new List <PlaylistSong>(), }; } CurrentEditingPlaylistLevelIds = new HashSet <string>(); foreach (PlaylistSong ps in CurrentEditingPlaylist.songs) { // Sometimes we cannot match a song if (ps.level == null) { continue; } CurrentEditingPlaylistLevelIds.Add(ps.level.levelID); } // Actually sort and filter //this.ProcessSongList(); // Signal complete if (SongLoader.CustomLevels.Count > 0) { didFinishProcessingSongs?.Invoke(SongLoader.CustomLevels); } timer.Stop(); Logger.Info("Updating songs infos took {0}ms", timer.ElapsedMilliseconds); }
/// <summary> /// Init this model. /// </summary> /// <param name="songSelectionMasterView"></param> /// <param name="songListViewController"></param> public void Init() { _settings = SongBrowserSettings.Load(); Logger.Info("Settings loaded, sorting mode is: {0}", _settings.sortMode); }
public IEnumerator GetInfoForSong(Playlist playlist, PlaylistSong song, Action <Song> songCallback) { string url = ""; bool _usingHash = false; if (!string.IsNullOrEmpty(song.key)) { url = $"{PluginConfig.beatsaverURL}/api/songs/detail/{song.key}"; if (!string.IsNullOrEmpty(playlist.customDetailUrl)) { url = playlist.customDetailUrl + song.key; } } else if (!string.IsNullOrEmpty(song.hash)) { url = $"{PluginConfig.beatsaverURL}/api/songs/search/hash/{song.hash}"; _usingHash = true; } else if (!string.IsNullOrEmpty(song.levelId)) { string hash = CustomHelpers.CheckHex(song.levelId.Substring(0, Math.Min(32, song.levelId.Length))); url = $"{PluginConfig.beatsaverURL}/api/songs/search/hash/{hash}"; _usingHash = true; } else { yield break; } UnityWebRequest www = UnityWebRequest.Get(url); www.timeout = 15; yield return(www.SendWebRequest()); if (www.isNetworkError || www.isHttpError) { Logger.Error($"Unable to connect to {PluginConfig.beatsaverURL}! " + (www.isNetworkError ? $"Network error: {www.error}" : (www.isHttpError ? $"HTTP error: {www.error}" : "Unknown error"))); } else { try { JSONNode node = JSON.Parse(www.downloadHandler.text); if (_usingHash) { if (node["songs"].Count == 0) { Logger.Error($"Song {song.songName} doesn't exist on BeatSaver!"); songCallback?.Invoke(null); yield break; } songCallback?.Invoke(Song.FromSearchNode(node["songs"][0])); } else { songCallback?.Invoke(new Song(node["song"])); } } catch (Exception e) { Logger.Exception("Unable to parse response! Exception: " + e); } } }
/// <summary> /// Handle Multiplayer Mode. /// Triggers when level select is clicked inside a host lobby. /// </summary> /// <param name="arg1"></param> /// <param name="arg2"></param> private void HandleMultiplayerModeSelection() { Logger.Trace("HandleCampaignModeSelection()"); HandleModeSelection(MainMenuViewController.MenuButton.Multiplayer); _songBrowserUI.Hide(); }
protected override void DidActivate(bool firstActivation, ActivationType type) { if (firstActivation && type == ActivationType.AddedToHierarchy) { gameObject.SetActive(true); rectTransform.sizeDelta = new Vector2(60f, 0f); _levelDetails = GetComponentsInChildren <StandardLevelDetailView>(true).First(x => x.name == "LevelDetail"); _levelDetails.gameObject.SetActive(true); RemoveCustomUIElements(rectTransform); Destroy(GetComponentsInChildren <LevelParamsPanel>().First(x => x.name == "LevelParamsPanel").gameObject); RectTransform yourStats = GetComponentsInChildren <RectTransform>(true).First(x => x.name == "Stats"); yourStats.gameObject.SetActive(true); //RectTransform buttonsRect = GetComponentsInChildren<RectTransform>().First(x => x.name == "PlayButtons"); //buttonsRect.anchoredPosition = new Vector2(0f, 6f); TextMeshProUGUI[] _textComponents = GetComponentsInChildren <TextMeshProUGUI>(); try { songNameText = _textComponents.First(x => x.name == "SongNameText"); _textComponents.First(x => x.name == "Title").text = "Playlist"; songNameText.enableWordWrapping = true; _textComponents.First(x => x.name == "Title" && x.transform.parent.name == "MaxCombo").text = "Author"; authorText = _textComponents.First(x => x.name == "Value" && x.transform.parent.name == "MaxCombo"); authorText.rectTransform.sizeDelta = new Vector2(24f, 0f); _textComponents.First(x => x.name == "Title" && x.transform.parent.name == "Highscore").text = "Total songs"; totalSongsText = _textComponents.First(x => x.name == "Value" && x.transform.parent.name == "Highscore"); _textComponents.First(x => x.name == "Title" && x.transform.parent.name == "MaxRank").text = "Downloaded"; downloadedSongsText = _textComponents.First(x => x.name == "Value" && x.transform.parent.name == "MaxRank"); } catch (Exception e) { Logger.Exception("Unable to convert detail view controller! Exception: " + e); } _selectButton = _levelDetails.playButton; _selectButton.SetButtonText(_selectButtonText); _selectButton.ToggleWordWrapping(false); _selectButton.onClick.RemoveAllListeners(); _selectButton.onClick.AddListener(() => { selectButtonPressed?.Invoke(_currentPlaylist); }); if (addDownloadButton) { _downloadButton = _levelDetails.practiceButton; _downloadButton.SetButtonIcon(Sprites.DownloadIcon); _downloadButton.onClick.RemoveAllListeners(); _downloadButton.onClick.AddListener(() => { downloadButtonPressed?.Invoke(_currentPlaylist); }); } else { Destroy(_levelDetails.practiceButton.gameObject); } coverImage = _levelDetails.GetPrivateField <RawImage>("_coverImage"); } }
/// <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(); // Check if we need to upgrade settings file favorites try { this.Settings.ConvertFavoritesToPlaylist(SongCore.Loader.CustomLevels); } catch (Exception e) { Logger.Exception("FAILED TO CONVERT FAVORITES TO PLAYLIST!", e); } // load the current editing playlist or make one if (CurrentEditingPlaylist == null && !String.IsNullOrEmpty(this.Settings.currentEditingPlaylistFile)) { Logger.Debug("Loading playlist for editing: {0}", this.Settings.currentEditingPlaylistFile); CurrentEditingPlaylist = Playlist.LoadPlaylist(this.Settings.currentEditingPlaylistFile); PlaylistsCollection.MatchSongsForPlaylist(CurrentEditingPlaylist, true); } if (CurrentEditingPlaylist == null) { Logger.Debug("Current editing playlist does not exit, create..."); CurrentEditingPlaylist = new Playlist { playlistTitle = "Song Browser Favorites", playlistAuthor = "SongBrowser", fileLoc = this.Settings.currentEditingPlaylistFile, image = Base64Sprites.SpriteToBase64(Base64Sprites.BeastSaberLogo), songs = new List <PlaylistSong>(), }; } CurrentEditingPlaylistLevelIds = new HashSet <string>(); foreach (PlaylistSong ps in CurrentEditingPlaylist.songs) { // Sometimes we cannot match a song string levelId = null; if (ps.level != null) { levelId = ps.level.levelID; } else if (!String.IsNullOrEmpty(ps.levelId)) { levelId = ps.levelId; } else { //Logger.Debug("MISSING SONG {0}", ps.songName); continue; } CurrentEditingPlaylistLevelIds.Add(levelId); } // 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); }
public IEnumerator DownloadSongCoroutine(Song songInfo) { songInfo.songQueueState = SongQueueState.Downloading; UnityWebRequest www; bool timeout = false; float time = 0f; UnityWebRequestAsyncOperation asyncRequest; try { www = UnityWebRequest.Get(songInfo.downloadUrl); asyncRequest = www.SendWebRequest(); } catch (Exception e) { Logger.Error(e); songInfo.songQueueState = SongQueueState.Error; songInfo.downloadingProgress = 1f; yield break; } while ((!asyncRequest.isDone || songInfo.downloadingProgress < 1f) && songInfo.songQueueState != SongQueueState.Error) { yield return(null); time += Time.deltaTime; if (time >= 5f && asyncRequest.progress <= float.Epsilon) { www.Abort(); timeout = true; Logger.Error("Connection timed out!"); } songInfo.downloadingProgress = asyncRequest.progress; } if (songInfo.songQueueState == SongQueueState.Error && (!asyncRequest.isDone || songInfo.downloadingProgress < 1f)) { www.Abort(); } if (www.isNetworkError || www.isHttpError || timeout || songInfo.songQueueState == SongQueueState.Error) { songInfo.songQueueState = SongQueueState.Error; Logger.Error("Unable to download song! " + (www.isNetworkError ? $"Network error: {www.error}" : (www.isHttpError ? $"HTTP error: {www.error}" : "Unknown error"))); } else { Logger.Log("Received response from BeatSaver.com..."); string docPath = ""; string customSongsPath = ""; byte[] data = www.downloadHandler.data; Stream zipStream = null; try { docPath = Application.dataPath; docPath = docPath.Substring(0, docPath.Length - 5); docPath = docPath.Substring(0, docPath.LastIndexOf("/")); customSongsPath = docPath + "/CustomSongs/" + songInfo.id + "/"; if (!Directory.Exists(customSongsPath)) { Directory.CreateDirectory(customSongsPath); } zipStream = new MemoryStream(data); Logger.Log("Downloaded zip!"); } catch (Exception e) { Logger.Exception(e); songInfo.songQueueState = SongQueueState.Error; yield break; } yield return(new WaitWhile(() => _extractingZip)); //because extracting several songs at once sometimes hangs the game Task extract = ExtractZipAsync(songInfo, zipStream, customSongsPath); yield return(new WaitWhile(() => !extract.IsCompleted)); songDownloaded?.Invoke(songInfo); } }
/// <summary> /// Sort the song list based on the settings. /// </summary> public void ProcessSongList() { Logger.Trace("ProcessSongList()"); // This has come in handy many times for debugging issues with Newest. /*foreach (BeatmapLevelSO level in _originalSongs) * { * if (_levelIdToCustomLevel.ContainsKey(level.levelID)) * { * Logger.Debug("HAS KEY {0}: {1}", _levelIdToCustomLevel[level.levelID].customSongInfo.path, level.levelID); * } * else * { * Logger.Debug("Missing KEY: {0}", level.levelID); * } * }*/ // TODO - remove as part of unifying song list interface if (_isPreviewLevelPack) { return; } if (_levelPackToSongs.Count == 0) { Logger.Debug("Cannot process songs yet, level packs have not been processed..."); return; } if (this._currentLevelPack == null || !this._levelPackToSongs.ContainsKey(this._currentLevelPack.packName)) { Logger.Debug("Cannot process songs yet, no level pack selected..."); return; } // Playlist filter will load the original songs. List <BeatmapLevelSO> unsortedSongs = null; List <BeatmapLevelSO> filteredSongs = null; if (this._settings.filterMode == SongFilterMode.Playlist && this.CurrentPlaylist != null) { unsortedSongs = null; } else { Logger.Debug("Using songs from level pack: {0}", this._currentLevelPack.packName); unsortedSongs = new List <BeatmapLevelSO>(_levelPackToSongs[this._currentLevelPack.packName]); } // filter Logger.Debug("Starting filtering songs..."); Stopwatch stopwatch = Stopwatch.StartNew(); switch (_settings.filterMode) { case SongFilterMode.Favorites: filteredSongs = FilterFavorites(); break; case SongFilterMode.Search: filteredSongs = FilterSearch(unsortedSongs); break; case SongFilterMode.Playlist: filteredSongs = FilterPlaylist(); break; case SongFilterMode.None: default: Logger.Info("No song filter selected..."); filteredSongs = unsortedSongs; break; } stopwatch.Stop(); Logger.Info("Filtering songs took {0}ms", stopwatch.ElapsedMilliseconds); // sort Logger.Debug("Starting to sort songs..."); stopwatch = Stopwatch.StartNew(); switch (_settings.sortMode) { case SongSortMode.Original: SortOriginal(filteredSongs); break; case SongSortMode.Newest: SortNewest(filteredSongs); break; case SongSortMode.Author: SortAuthor(filteredSongs); break; case SongSortMode.PlayCount: SortPlayCount(filteredSongs); break; case SongSortMode.PP: SortPerformancePoints(filteredSongs); break; case SongSortMode.Difficulty: SortDifficulty(filteredSongs); break; case SongSortMode.Random: SortRandom(filteredSongs); break; case SongSortMode.Default: default: SortSongName(filteredSongs); break; } if (this.Settings.invertSortResults && _settings.sortMode != SongSortMode.Random) { _sortedSongs.Reverse(); } stopwatch.Stop(); Logger.Info("Sorting songs took {0}ms", stopwatch.ElapsedMilliseconds); this.OverwriteCurrentLevelPack(); //_sortedSongs.ForEach(x => Logger.Debug(x.levelID)); }
/// <summary> /// Sort the song list based on the settings. /// </summary> public void ProcessSongList(LevelPackLevelsViewController levelsViewController) { Logger.Trace("ProcessSongList()"); List <IPreviewBeatmapLevel> unsortedSongs = null; List <IPreviewBeatmapLevel> filteredSongs = null; List <IPreviewBeatmapLevel> sortedSongs = null; // Abort if (levelsViewController.levelPack == null) { Logger.Debug("Cannot process songs yet, no level pack selected..."); return; } // fetch unsorted songs. // playlists always use customsongs if (this._settings.filterMode == SongFilterMode.Playlist && this.CurrentPlaylist != null) { unsortedSongs = null; } else { Logger.Debug("Using songs from level pack: {0}", levelsViewController.levelPack.packID); unsortedSongs = levelsViewController.levelPack.beatmapLevelCollection.beatmapLevels.ToList(); } // filter Logger.Debug($"Starting filtering songs by {_settings.filterMode}"); Stopwatch stopwatch = Stopwatch.StartNew(); switch (_settings.filterMode) { case SongFilterMode.Favorites: filteredSongs = FilterFavorites(); break; case SongFilterMode.Search: filteredSongs = FilterSearch(unsortedSongs); break; case SongFilterMode.Playlist: filteredSongs = FilterPlaylist(); break; case SongFilterMode.Ranked: filteredSongs = FilterRanked(unsortedSongs, true, false); break; case SongFilterMode.Unranked: filteredSongs = FilterRanked(unsortedSongs, false, true); break; case SongFilterMode.Custom: Logger.Info("Song filter mode set to custom. Deferring filter behaviour to another mod."); filteredSongs = CustomFilterHandler != null?CustomFilterHandler.Invoke(levelsViewController.levelPack) : unsortedSongs; break; case SongFilterMode.None: default: Logger.Info("No song filter selected..."); filteredSongs = unsortedSongs; break; } stopwatch.Stop(); Logger.Info("Filtering songs took {0}ms", stopwatch.ElapsedMilliseconds); // sort Logger.Debug("Starting to sort songs..."); stopwatch = Stopwatch.StartNew(); switch (_settings.sortMode) { case SongSortMode.Original: sortedSongs = SortOriginal(filteredSongs); break; case SongSortMode.Newest: sortedSongs = SortNewest(filteredSongs); break; case SongSortMode.Author: sortedSongs = SortAuthor(filteredSongs); break; case SongSortMode.UpVotes: sortedSongs = SortUpVotes(filteredSongs); break; case SongSortMode.PlayCount: sortedSongs = SortBeatSaverPlayCount(filteredSongs); break; case SongSortMode.Rating: sortedSongs = SortBeatSaverRating(filteredSongs); break; case SongSortMode.Heat: sortedSongs = SortBeatSaverHeat(filteredSongs); break; case SongSortMode.YourPlayCount: sortedSongs = SortPlayCount(filteredSongs); break; case SongSortMode.PP: sortedSongs = SortPerformancePoints(filteredSongs); break; case SongSortMode.Stars: sortedSongs = SortStars(filteredSongs); break; case SongSortMode.Difficulty: sortedSongs = SortDifficulty(filteredSongs); break; case SongSortMode.Random: sortedSongs = SortRandom(filteredSongs); break; case SongSortMode.Default: default: sortedSongs = SortSongName(filteredSongs); break; } if (this.Settings.invertSortResults && _settings.sortMode != SongSortMode.Random) { sortedSongs.Reverse(); } stopwatch.Stop(); Logger.Info("Sorting songs took {0}ms", stopwatch.ElapsedMilliseconds); // Asterisk the pack name so it is identifable as filtered. var packName = levelsViewController.levelPack.packName; if (!packName.EndsWith("*") && _settings.filterMode != SongFilterMode.None) { packName += "*"; } BeatmapLevelPack levelPack = new BeatmapLevelPack(SongBrowserModel.FilteredSongsPackId, packName, levelsViewController.levelPack.coverImage, new BeatmapLevelCollection(sortedSongs.ToArray())); levelsViewController.SetData(levelPack); //_sortedSongs.ForEach(x => Logger.Debug(x.levelID)); }
/// <summary> /// Sorting returns original list. /// </summary> /// <param name="levels"></param> /// <returns></returns> private List <IPreviewBeatmapLevel> SortOriginal(List <IPreviewBeatmapLevel> levels) { Logger.Info("Sorting song list as original"); return(levels); }
private void SortOriginal(List <BeatmapLevelSO> levels) { Logger.Info("Sorting song list as original"); _sortedSongs = levels; }
public bool DeleteSong(Song song) { bool zippedSong = false; string path = ""; CustomLevel level = SongLoader.CustomLevels.FirstOrDefault(x => x.levelID.StartsWith(song.hash)); if (string.IsNullOrEmpty(song.path)) { if (level != null) { path = level.customSongInfo.path; } } else { path = song.path; } if (string.IsNullOrEmpty(path)) { return(false); } if (!Directory.Exists(path)) { return(false); } if (path.Contains("/.cache/")) { zippedSong = true; } if (zippedSong) { Logger.Log("Deleting \"" + path.Substring(path.LastIndexOf('/')) + "\"..."); if (PluginConfig.deleteToRecycleBin) { FileOperationAPIWrapper.MoveToRecycleBin(path); } else { Directory.Delete(path, true); } string songHash = Directory.GetParent(path).Name; try { if (Directory.GetFileSystemEntries(path.Substring(0, path.LastIndexOf('/'))).Length == 0) { Logger.Log("Deleting empty folder \"" + path.Substring(0, path.LastIndexOf('/')) + "\"..."); Directory.Delete(path.Substring(0, path.LastIndexOf('/')), false); } } catch { Logger.Warning("Can't find or delete empty folder!"); } string docPath = Application.dataPath; docPath = docPath.Substring(0, docPath.Length - 5); docPath = docPath.Substring(0, docPath.LastIndexOf("/")); string customSongsPath = docPath + "/CustomSongs/"; string hash = ""; foreach (string file in Directory.GetFiles(customSongsPath, "*.zip")) { if (CreateMD5FromFile(file, out hash)) { if (hash == songHash) { File.Delete(file); break; } } } } else { Logger.Log("Deleting \"" + path.Substring(path.LastIndexOf('/')) + "\"..."); if (PluginConfig.deleteToRecycleBin) { FileOperationAPIWrapper.MoveToRecycleBin(path); } else { Directory.Delete(path, true); } try { if (Directory.GetFileSystemEntries(path.Substring(0, path.LastIndexOf('/'))).Length == 0) { Logger.Log("Deleting empty folder \"" + path.Substring(0, path.LastIndexOf('/')) + "\"..."); Directory.Delete(path.Substring(0, path.LastIndexOf('/')), false); } } catch { Logger.Warning("Unable to delete empty folder!"); } } if (level != null) { SongLoader.Instance.RemoveSongWithLevelID(level.levelID); } Logger.Log($"{_alreadyDownloadedSongs.RemoveAll(x => x.Compare(song))} song removed"); return(true); }