//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; } } }
public static async void PlaySong(IPreviewBeatmapLevel level, BeatmapCharacteristicSO characteristic, BeatmapDifficulty difficulty, OverrideEnvironmentSettings overrideEnvironmentSettings = null, ColorScheme colorScheme = null, GameplayModifiers gameplayModifiers = null, PlayerSpecificSettings playerSettings = null, Action <StandardLevelScenesTransitionSetupDataSO, LevelCompletionResults> songFinishedCallback = null) { Action <IBeatmapLevel> SongLoaded = (loadedLevel) => { MenuTransitionsHelper _menuSceneSetupData = Resources.FindObjectsOfTypeAll <MenuTransitionsHelper>().First(); _menuSceneSetupData.StartStandardLevel( loadedLevel.beatmapLevelData.GetDifficultyBeatmap(characteristic, difficulty), overrideEnvironmentSettings, colorScheme, gameplayModifiers ?? new GameplayModifiers(), playerSettings ?? new PlayerSpecificSettings(), null, "Menu", false, null, (standardLevelScenesTransitionSetupData, results) => songFinishedCallback?.Invoke(standardLevelScenesTransitionSetupData, results) ); }; if ((level is PreviewBeatmapLevelSO && await HasDLCLevel(level.levelID)) || level is CustomPreviewBeatmapLevel) { Logger.Debug("Loading DLC/Custom level..."); var result = await GetLevelFromPreview(level); if (result != null && !(result?.isError == true)) { //HTTPstatus requires cover texture to be applied in here, and due to a fluke //of beat saber, it's not applied when the level is loaded, but it *is* //applied to the previewlevel it's loaded from var loadedLevel = result?.beatmapLevel; loadedLevel.SetField("_coverImageTexture2D", level.GetField <Texture2D>("_coverImageTexture2D")); SongLoaded(loadedLevel); } } else if (level is BeatmapLevelSO) { Logger.Debug("Reading OST data without songloader..."); SongLoaded(level as IBeatmapLevel); } else { Logger.Debug($"Skipping unowned DLC ({level.songName})"); } }
//Returns the closest difficulty to the one provided, preferring lower difficulties first if any exist public static IDifficultyBeatmap GetClosestDifficultyPreferLower(IBeatmapLevel level, BeatmapDifficulty difficulty, string characteristic) { //First, look at the characteristic parameter. If there's something useful in there, we try to use it, but fall back to Standard var desiredCharacteristic = level.previewDifficultyBeatmapSets.FirstOrDefault(x => x.beatmapCharacteristic.serializedName == characteristic).beatmapCharacteristic ?? level.previewDifficultyBeatmapSets.First().beatmapCharacteristic; IDifficultyBeatmap[] availableMaps = level .beatmapLevelData .difficultyBeatmapSets .FirstOrDefault(x => x.beatmapCharacteristic.serializedName == desiredCharacteristic.serializedName) .difficultyBeatmaps .OrderBy(x => x.difficulty) .ToArray(); IDifficultyBeatmap ret = availableMaps.FirstOrDefault(x => x.difficulty == difficulty); if (ret is CustomDifficultyBeatmap) { var extras = Collections.RetrieveExtraSongData(ret.level.levelID); var requirements = extras?._difficulties.First(x => x._difficulty == ret.difficulty).additionalDifficultyData._requirements; Logger.Debug($"{ret.level.songName} is a custom level, checking for requirements on {ret.difficulty}..."); if ( (requirements?.Count() > 0) && (!requirements?.ToList().All(x => Collections.capabilities.Contains(x)) ?? false) ) { ret = null; } Logger.Debug((ret == null ? "Requirement not met." : "Requirement met!")); } if (ret == null) { ret = GetLowerDifficulty(availableMaps, difficulty, desiredCharacteristic); } if (ret == null) { ret = GetHigherDifficulty(availableMaps, difficulty, desiredCharacteristic); } return(ret); }
//Returns the next-lowest difficulty to the one provided private static IDifficultyBeatmap GetLowerDifficulty(IDifficultyBeatmap[] availableMaps, BeatmapDifficulty difficulty, BeatmapCharacteristicSO characteristic) { var ret = availableMaps.TakeWhile(x => x.difficulty < difficulty).LastOrDefault(); if (ret is CustomDifficultyBeatmap) { var extras = Collections.RetrieveExtraSongData(ret.level.levelID); var requirements = extras?._difficulties.First(x => x._difficulty == ret.difficulty).additionalDifficultyData._requirements; Logger.Debug($"{ret.level.songName} is a custom level, checking for requirements on {ret.difficulty}..."); if ( (requirements?.Count() > 0) && (!requirements?.ToList().All(x => Collections.capabilities.Contains(x)) ?? false) ) { ret = null; } Logger.Debug((ret == null ? "Requirement not met." : "Requirement met!")); } return(ret); }
//BSUtils: disable gameplay-modifying plugins private void BSUtilsDisableOtherPlugins() { BS_Utils.Gameplay.Gamemode.NextLevelIsIsolated("EventPlugin"); Logger.Debug("Disabled game-modifying plugins through bs_utils :)"); }
//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); } } }