//To fix the bug explained in CustomLevelStaticData.cs private void OnDidSelectSongEvent(SongListViewController songListViewController) { var song = CustomLevelStaticDatas.FirstOrDefault(x => x.levelId == songListViewController.levelId); if (song == null) { return; } if (song.difficultyLevels.All(x => x.difficulty != _songSelectionView.difficulty)) { var isDiffSelected = ReflectionUtil.GetPrivateField <bool>(_difficultyView, "_difficultySelected"); if (!isDiffSelected) { return; } //The new selected song does not have the current difficulty selected var firstDiff = song.difficultyLevels.FirstOrDefault(); if (firstDiff == null) { return; } ReflectionUtil.SetPrivateField(_songSelectionView, "_difficulty", firstDiff.difficulty); } }
public void OpenSongsList(string songToSelectWhenLoaded = null) { if (songListViewController == null) { songListViewController = BeatSaberUI.CreateViewController <SongListViewController>(); } if (_bottomViewController == null) { _bottomViewController = BeatSaberUI.CreateViewController <BottomViewController>(); } if (_resultsViewController == null) { _resultsViewController = Resources.FindObjectsOfTypeAll <ResultsViewController>().First(); } if (_playerDataModel == null) { _playerDataModel = Resources.FindObjectsOfTypeAll <PlayerDataModel>().First(); } if (_menuLightsManager == null) { _menuLightsManager = Resources.FindObjectsOfTypeAll <MenuLightsManager>().First(); } if (_soloFreePlayFlowCoordinator == null) { _soloFreePlayFlowCoordinator = Resources.FindObjectsOfTypeAll <SoloFreePlayFlowCoordinator>().First(); } if (_campaignFlowCoordinator == null) { _campaignFlowCoordinator = Resources.FindObjectsOfTypeAll <CampaignFlowCoordinator>().First(); } if (_alwaysOwnedContent == null) { _alwaysOwnedContent = Resources.FindObjectsOfTypeAll <AlwaysOwnedContentSO>().First(); } if (_primaryLevelCollection == null) { _primaryLevelCollection = _alwaysOwnedContent.alwaysOwnedPacks.First(x => x.packID == OstHelper.packs[0].PackID).beatmapLevelCollection as BeatmapLevelCollectionSO; } if (_secondaryLevelCollection == null) { _secondaryLevelCollection = _alwaysOwnedContent.alwaysOwnedPacks.First(x => x.packID == OstHelper.packs[1].PackID).beatmapLevelCollection as BeatmapLevelCollectionSO; } if (_tertiaryLevelCollection == null) { _tertiaryLevelCollection = _alwaysOwnedContent.alwaysOwnedPacks.First(x => x.packID == OstHelper.packs[2].PackID).beatmapLevelCollection as BeatmapLevelCollectionSO; } if (_extrasLevelCollection == null) { _extrasLevelCollection = _alwaysOwnedContent.alwaysOwnedPacks.First(x => x.packID == OstHelper.packs[3].PackID).beatmapLevelCollection as BeatmapLevelCollectionSO; } if (!songListViewController.isInViewControllerHierarchy || !songListViewController.isActiveAndEnabled) { SetViewControllersToNavigationController(_mainModNavigationController, new ViewController[] { songListViewController }); songListViewController.SelectWhenLoaded(songToSelectWhenLoaded); songListViewController.SongListRowSelected += SongListRowSelected; songListViewController.ReloadPressed += () => ReloadServerData(); ReloadServerData(); } }
public override void ViewDidLoad() { base.ViewDidLoad(); View.Identifier = "SearchResultsXibView"; View.TranslatesAutoresizingMaskIntoConstraints = false; songListViewController = new SongListViewController(); var itemIndex = tabView.IndexOf(new NSString("songs")); var tabViewItem = tabView.Items [itemIndex]; tabViewItem.View.AddSubview(songListViewController.View); tabViewItem.View.AddConstraints(FillHorizontal(songListViewController.View, false)); tabViewItem.View.AddConstraints(FillVertical(songListViewController.View, false)); songListViewController.Tracks = viewModel.Search.Tracks; songListViewController.SongDoubleClicked += DidSongDoubleClicked; tabGroup.AddTab("songs", "Playlist", "Tracks"); tabGroup.AddTab("videos", "Video", "Videos"); tabGroup.AddTab("artists", "Artist", "Artists"); tabGroup.ActiveTabChanged += OnActiveTabChanged; tabGroup.ActivateTab("songs"); artistsCollectionView.Delegate = this; artistsCollectionView.Selectable = true; artistsCollectionView.RegisterNib(new NSNib("ArtistItemView", NSBundle.MainBundle), "ArtistItemView"); artistsCollectionView.RegisterClassForItem(typeof(ArtistItemView), "ArtistItemView"); artistsCollectionView.ReloadData(); }
public override void ViewDidLoad() { headerViewController = new HeaderViewController(); songListViewController = new SongListViewController(); albumListViewController = new AlbumListViewController(); albumListViewController.SongDoubleClicked += PlaySong; albumListViewPlaceholder.PresentView(albumListViewController.View); playlistsViewController = new AlbumListViewController(); playlistsViewController.SongDoubleClicked += PlaySong; playlistViewPlaceholder.PresentView(playlistsViewController.View); songListViewController.SongDoubleClicked += PlaySong; songListViewController.DisableScroll(); songListViewPlaceholder.PresentView(songListViewController.TableView); songListViewPlaceholder.InvalidateIntrinsicContentSize(); headerViewPlaceholder.PresentView(headerViewController.View); headerViewPlaceholder.InvalidateIntrinsicContentSize(); headerViewController.TabGroup.AddTab("popularSongs", "Playlist", "Popular Songs"); headerViewController.TabGroup.AddTab("albums", "Star", "Albums"); headerViewController.TabGroup.AddTab("playlists", "Playlist", "Playlists"); headerViewController.TabGroup.ActivateTab("popularSongs"); headerViewController.TabGroup.ActiveTabChanged += DidActiveTabChanged; this.WhenAnyValue(val => val.ViewModel) .Where(val => val != null) .DistinctUntilChanged() .Subscribe(vm => BindViewModel()); }
//Starts the necessary coroutine chain to make the mod functional public static void GetData( SongListViewController slvc, string userId, Action <Player> userDataGottenCallback = null, Action <List <Team> > teamsGottenCallback = null, Action <List <Song> > songsGottenCallback = null ) { SharedCoroutineStarter.instance.StartCoroutine(GetAllData(slvc, userId, userDataGottenCallback, teamsGottenCallback, songsGottenCallback)); }
private void SceneManagerOnActiveSceneChanged(Scene arg0, Scene scene) { StartCoroutine(WaitRemoveScores()); SongListViewController songListController = Resources.FindObjectsOfTypeAll <SongListViewController>() .FirstOrDefault(); if (songListController == null) { return; } songListController.didSelectSongEvent += OnDidSelectSongEvent; _songSelectionView = Resources.FindObjectsOfTypeAll <SongSelectionMasterViewController>().FirstOrDefault(); _difficultyView = Resources.FindObjectsOfTypeAll <DifficultyViewController>().FirstOrDefault(); }
//GET the teams from the server private static IEnumerator GetTeams(SongListViewController slvc, Action <List <Team> > teamsGottenCallback = null) { UnityWebRequest www = UnityWebRequest.Get($"{discordCommunityApi}/teams/"); Logger.Debug($"REQUESTING TEAMS: {discordCommunityApi}/teams/"); www.timeout = 30; yield return(www.SendWebRequest()); if (www.isNetworkError || www.isHttpError) { Logger.Error($"Error getting teams: {www.error}"); slvc.ErrorHappened($"Error getting teams: {www.error}"); } else { try { //Clear out existing teams Team.allTeams.Clear(); //Get the list of songs to download, and map out the song ids to the corresponding gamemodes var node = JSON.Parse(www.downloadHandler.text); foreach (var team in node) { var teamObject = new Team( team.Key, team.Value["teamName"], team.Value["captainId"], team.Value["color"], team.Value["requiredTokens"], team.Value["nextPromotion"] ); Team.allTeams.Add(teamObject); } teamsGottenCallback?.Invoke(Team.allTeams); } catch (Exception e) { Logger.Error($"Error parsing teams data: {e}"); slvc.ErrorHappened($"Error parsing teams data: {e}"); yield break; } } }
//GET the user's profile data from the server private static IEnumerator GetUserData(SongListViewController slvc, string userId, Action <Player> userDataGottenCallback = null) { UnityWebRequest www = UnityWebRequest.Get($"{discordCommunityApi}/playerstats/{userId}"); Logger.Debug($"GETTING PLAYER DATA: {discordCommunityApi}/playerstats/{userId}"); www.timeout = 30; yield return(www.SendWebRequest()); if (www.isNetworkError || www.isHttpError) { Logger.Error($"Error getting player stats: {www.error}"); slvc.ErrorHappened($"Error getting player stats: {www.error}"); } else { try { var node = JSON.Parse(www.downloadHandler.text); //If there is a message from the server, display it if (node["message"] != null && node["message"].ToString().Length > 1) { slvc.ErrorHappened(node["message"]); yield break; } //If the client is out of date, show update message if (VersionCode < Convert.ToInt32(node["version"].Value)) { slvc.ErrorHappened($"Version {SharedConstructs.Version} is now out of date. Please download the newest one from the Discord."); } Player.Instance.Team = node["team"]; Player.Instance.Tokens = Convert.ToInt32(node["tokens"].Value); Player.Instance.ServerOptions = (ServerFlags)Convert.ToInt32(node["serverSettings"].Value); userDataGottenCallback?.Invoke(Player.Instance); } catch (Exception e) { Logger.Error($"Error parsing playerstats data: {e}"); slvc.ErrorHappened($"Error parsing playerstats data: {e}"); yield break; } } }
//Gets all relevant data for the mod to work private static IEnumerator GetAllData( SongListViewController slvc, string userId, Action <Player> userDataGottenCallback = null, Action <List <Team> > teamsGottenCallback = null, Action <List <Song> > songsGottenCallback = null ) { yield return(SharedCoroutineStarter.instance.StartCoroutine(GetUserData(slvc, userId, userDataGottenCallback))); yield return(SharedCoroutineStarter.instance.StartCoroutine(GetTeams(slvc, teamsGottenCallback))); if (!slvc.errorHappened && !slvc.HasSongs()) { yield return(SharedCoroutineStarter.instance.StartCoroutine(GetSongs(slvc, userId, songsGottenCallback))); } }
//To fix the bug explained in CustomLevelStaticData.cs private void OnDidSelectSongEvent(SongListViewController songListViewController) { try { CustomLevelStaticData song = CustomLevelStaticDatas.FirstOrDefault(x => x.levelId == songListViewController.levelId); if (song == null) { return; } if (!LoadIfNotLoaded(song)) { Logger.Log("Song was modified, updated leaderboard ID to " + song.levelId); songListViewController.SelectSong(_levels.FirstIndexWhere(data => data.levelId == song.levelId)); return; } if (song.difficultyLevels.All(x => x.difficulty != _songSelectionView.difficulty)) { bool isDiffSelected = ReflectionUtil.GetPrivateField <bool>(_difficultyView, "_difficultySelected"); if (!isDiffSelected) { return; } //The new selected song does not have the current difficulty selected LevelStaticData.DifficultyLevel firstDiff = song.difficultyLevels.FirstOrDefault(); if (firstDiff == null) { return; } ReflectionUtil.SetPrivateField(_songSelectionView, "_difficulty", firstDiff.difficulty); } } catch (Exception e) { Logger.Log(e.ToString()); } }
//Download songs. Taken from BeatSaberMultiplayer //availableSongs: List of IBeatmapLevel which may hold levels already approved for display //downloadQueue: List of beatsaver ids representing songs left to download //completedDownloads: List of beatsaver ids representing songs that have successfully downloaded //songId: The song this instance of the Coroutine is supposed to download //slvc: The song list view controller to display the downloaded songs to private static IEnumerator DownloadSongs(string songHash, SongListViewController slvc) { UnityWebRequest www = UnityWebRequest.Get($"{beatSaverDownloadUrl}{songHash}"); #if BETA Logger.Info($"DOWNLOADING: {beatSaverDownloadUrl}{songHash}"); #endif bool timeout = false; float time = 0f; www.SetRequestHeader("user-agent", @"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.106 Safari/537.36"); UnityWebRequestAsyncOperation asyncRequest = www.SendWebRequest(); while (!asyncRequest.isDone || asyncRequest.progress < 1f) { yield return(null); time += Time.deltaTime; if (time >= 15f && asyncRequest.progress == 0f) { www.Abort(); timeout = true; } } if (www.isNetworkError || www.isHttpError || timeout) { Logger.Error($"Error downloading song {songHash}: {www.error}"); slvc.ErrorHappened($"Error downloading song {songHash}: {www.error}"); } else { //Logger.Info("Received response from BeatSaver.com..."); string zipPath = ""; string customSongsPath = CustomLevelPathHelper.customLevelsDirectoryPath; string customSongPath = ""; byte[] data = www.downloadHandler.data; try { customSongPath = customSongsPath + "/" + songHash + "/"; zipPath = customSongPath + songHash + ".zip"; if (!Directory.Exists(customSongPath)) { Directory.CreateDirectory(customSongPath); } File.WriteAllBytes(zipPath, data); //Logger.Info("Downloaded zip file!"); } catch (Exception e) { Logger.Error($"Error writing zip: {e}"); slvc.ErrorHappened($"Error writing zip: {e}"); yield break; } //Logger.Info("Extracting..."); try { ZipFile.ExtractToDirectory(zipPath, customSongPath); } catch (Exception e) { Logger.Error($"Unable to extract ZIP! Exception: {e}"); slvc.ErrorHappened($"Unable to extract ZIP! Exception: {e}"); yield break; } try { File.Delete(zipPath); } catch (IOException e) { Logger.Warning($"Unable to delete zip! Exception: {e}"); slvc.ErrorHappened($"Unable to delete zip! Exception: {e}"); yield break; } Logger.Success($"Downloaded!"); } }
//GET the songs from the server, then start the Download coroutine to download and display them //TODO: Time complexity here is a mess. private static IEnumerator GetSongs(SongListViewController slvc, string userId, Action <List <Song> > songsGottenCallback = null) { UnityWebRequest www = UnityWebRequest.Get($"{discordCommunityApi}/songs/{userId}/"); Logger.Debug($"REQUESTING SONGS: {discordCommunityApi}/songs/{userId}/"); www.timeout = 30; yield return(www.SendWebRequest()); if (www.isNetworkError || www.isHttpError) { Logger.Error($"Error getting songs: {www.error}"); slvc.ErrorHappened($"Error getting songs: {www.error}"); } else { List <Song> songs = new List <Song>(); try { //Get the list of songs to download, and map out the song ids to the corresponding gamemodes var node = JSON.Parse(www.downloadHandler.text); foreach (var id in node) { var newSong = new Song() { Hash = id.Value["songHash"], SongName = id.Value["songName"], GameOptions = (GameOptions)Convert.ToInt32(id.Value["gameOptions"].ToString()), PlayerOptions = (PlayerOptions)Convert.ToInt32(id.Value["playerOptions"].ToString()), Difficulty = (LevelDifficulty)Convert.ToInt32(id.Value["difficulty"].ToString()), Characteristic = id.Value["characteristic"] }; Logger.Debug($"ADDING SONG: {newSong.SongName} {newSong.Difficulty} {newSong.Characteristic}"); songs.Add(newSong); } } catch (Exception e) { Logger.Error($"Error parsing getsong data: {e}"); slvc.ErrorHappened($"Error parsing getsong data: {e}"); yield break; } //If we got songs, filter them as neccessary then download any we don't have List <Song> availableSongs = new List <Song>(); //Filter out songs we already have and OSTS IEnumerable <Song> osts = songs.Where(x => OstHelper.IsOst(x.Hash)); IEnumerable <Song> alreadyHave = songs.Where(x => Collections.songWithHashPresent(x.Hash.ToUpper())); //Loads a level from a song instance, populates the Beatmap property and adds to the available list Action <Song> loadLevel = (song) => { if (Collections.songWithHashPresent(song.Hash.ToUpper())) { var levelId = Collections.levelIDsForHash(song.Hash).First(); var customPreview = Loader.CustomLevelsCollection.beatmapLevels.First(x => x.levelID == levelId) as CustomPreviewBeatmapLevel; song.PreviewBeatmap = customPreview; //TODO: Figure out proper async-ness here /*var beatmapLevelResult = Task.Run(async () => await SongUtils.GetLevelFromPreview(customPreview)); * beatmapLevelResult.Wait(); * * //TODO: add characteristic name field to the song data stored in the server * song.Beatmap = SongUtils.GetClosestDifficultyPreferLower(beatmapLevelResult.Result?.beatmapLevel, (BeatmapDifficulty)song.Difficulty); * availableSongs.Add(song);*/ } else { slvc.ErrorHappened($"Could not load level {song.SongName}"); } }; //Load the preview levels for what we have foreach (Song song in osts) { foreach (IBeatmapLevelPack pack in Loader.BeatmapLevelsModelSO.allLoadedBeatmapLevelPackCollection.beatmapLevelPacks) { var foundLevel = pack.beatmapLevelCollection.beatmapLevels.FirstOrDefault(y => y.levelID.ToLower() == song.Hash.ToLower()); if (foundLevel != null) { song.PreviewBeatmap = foundLevel; } } } foreach (Song song in alreadyHave) { loadLevel(song); } //Of what we already have, add the Levels to the availableSongs list availableSongs.AddRange(alreadyHave); availableSongs.AddRange(osts); //Remove what we already have from the download queue songs.RemoveAll(x => availableSongs.Contains(x)); //Don't redownload //Download the things we don't have, or if we have everything, show the menu if (songs.Count > 0) { List <IEnumerator> downloadCoroutines = new List <IEnumerator>(); songs.ForEach(x => { downloadCoroutines.Add(DownloadSongs(x.Hash, slvc)); }); //Wait for the all downloads to finish yield return(SharedCoroutineStarter.instance.StartCoroutine(new ParallelCoroutine().ExecuteCoroutines(downloadCoroutines.ToArray()))); Action <Loader, Dictionary <string, CustomPreviewBeatmapLevel> > songsLoaded = (_, __) => { //Now that they're refreshed, we can populate their beatmaps and add them to the available list songs.ForEach(x => loadLevel(x)); songsGottenCallback?.Invoke(availableSongs.Union(songs).ToList()); }; Loader.SongsLoadedEvent -= songsLoaded; Loader.SongsLoadedEvent += songsLoaded; Loader.Instance.RefreshSongs(false); } else { songsGottenCallback?.Invoke(availableSongs); } } }