public static VoteStatus GetVoteStatus(BeatmapDetails details) { if (_votedSongs == null || details.IsOST) { return(VoteStatus.NoVote); } string levelHash = details.GetLevelHash(); SongVote?songVote = null; if (_votedSongs.ContainsKey(levelHash.ToLower())) { songVote = _votedSongs[levelHash.ToLower()]; } else if (_votedSongs.ContainsKey(levelHash.ToUpper())) { songVote = _votedSongs[levelHash.ToUpper()]; } if (songVote.HasValue) { return(songVote.Value.voteType == VoteType.Upvote ? VoteStatus.Upvoted : VoteStatus.Downvoted); } else { return(VoteStatus.NoVote); } }
public override void Reset() { base.Reset(); Add(details = new BeatmapDetails { RelativeSizeAxes = Axes.Both, Padding = new MarginPadding(150), Beatmap = new BeatmapInfo { Version = "VisualTest", Metadata = new BeatmapMetadata { Source = "Some guy", Tags = "beatmap metadata example with a very very long list of tags and not much creativity", }, Difficulty = new BeatmapDifficulty { CircleSize = 7, ApproachRate = 3.5f, OverallDifficulty = 5.7f, DrainRate = 1, }, StarDifficulty = 5.3f, Metrics = new BeatmapMetrics { Ratings = Enumerable.Range(0, 10), Fails = Enumerable.Range(lastRange, 100).Select(i => i % 12 - 6), Retries = Enumerable.Range(lastRange - 3, 100).Select(i => i % 12 - 6), }, }, }); AddRepeatStep("fail values", newRetryAndFailValues, 10); }
public void FilterSongList(ref List <BeatmapDetails> detailsList) { if (!_isInitialized || (!_oneSaberAppliedValue && !_lightshowAppliedValue && _mappingExtensionsAppliedValue == SongRequirement.Off)) { return; } for (int i = 0; i < detailsList.Count;) { BeatmapDetails beatmap = detailsList[i]; List <CustomPreviewBeatmapLevel> customLevels = null; if (_mappingExtensionsAppliedValue != SongRequirement.Off) { customLevels = Loader.CustomLevels.Values.ToList(); } if (_lightshowAppliedValue && !beatmap.DifficultyBeatmapSets.Any(diffSet => diffSet.DifficultyBeatmaps.Any(diff => diff.NotesCount == 0))) { detailsList.RemoveAt(i); } else if (_oneSaberAppliedValue && !beatmap.DifficultyBeatmapSets.Any(diffSet => diffSet.CharacteristicName == "OneSaber")) { detailsList.RemoveAt(i); } else if (_mappingExtensionsAppliedValue != SongRequirement.Off && !beatmap.IsOST) { // remove songs that somehow aren't OST, but also aren't custom levels handled by SongCore CustomPreviewBeatmapLevel customLevel = customLevels.FirstOrDefault(x => x.levelID == beatmap.LevelID); if (customLevel == null) { detailsList.RemoveAt(i); continue; } ExtraSongData songData = Collections.RetrieveExtraSongData(Hashing.GetCustomLevelHash(customLevel), customLevel.customLevelPath); if (songData == null) { detailsList.RemoveAt(i); continue; } bool required = songData._difficulties?.Any(x => x.additionalDifficultyData?._requirements?.Any(y => y == "Mapping Extensions") == true) == true; if ((_mappingExtensionsAppliedValue == SongRequirement.Required && !required) || (_mappingExtensionsAppliedValue == SongRequirement.NotRequired && required)) { detailsList.RemoveAt(i); continue; } // passes check, requires mapping extensions ++i; } else { ++i; } } }
/// <summary> /// Returns the highest rank achieved for a level and a given list of difficulties. /// NOTE: this method obtains the rank by performing a calculation on the set score and assumes that no modifiers were used. /// </summary> /// <param name="level">The level to search through.</param> /// <param name="difficulties">A list of BeatmapDifficulties to search through. Use null to search through all difficulties.</param> /// <param name="characteristics">A list of characteristics to search through. Each characteristic is represented by its serialized string. /// Use null to search through all characteristics.</param> /// <param name="playerName">The name of the player on the local leaderboards (optional).</param> /// <returns>The highest RankModel.Rank enum found for the selected difficulties, or null if the level has not yet been completed.</returns> public RankModel.Rank?GetHighestRankForLevel(BeatmapDetails level, IEnumerable <BeatmapDifficulty> difficulties = null, IEnumerable <string> characteristics = null, string playerName = null) { if (difficulties == null) { difficulties = AllDifficulties; } if (characteristics == null) { characteristics = AllCharacteristicStrings; } // get any level duplicates List <string> duplicateLevelIDs = GetActualLevelIDs(level.LevelID); StringBuilder sb = new StringBuilder(); RankModel.Rank?highestRank = null; foreach (var levID in duplicateLevelIDs) { foreach (var characteristic in AllCharacteristicStrings) { var simplifiedChar = level.DifficultyBeatmapSets.FirstOrDefault(x => x.CharacteristicName == characteristic || (characteristic == "" && x.CharacteristicName == "Standard")); if (simplifiedChar == null) { continue; } foreach (var difficulty in difficulties) { var simplifiedDiff = simplifiedChar.DifficultyBeatmaps.FirstOrDefault(x => x.Difficulty == difficulty); if (simplifiedDiff == null) { continue; } sb.Clear(); sb.Append(levID); sb.Append(characteristic); sb.Append(difficulty.ToString()); string leaderboardID = sb.ToString(); var scores = _localLeaderboardsModel.GetScores(leaderboardID, LocalLeaderboardsModel.LeaderboardType.AllTime); int maxRawScore = ScoreModel.MaxRawScoreForNumberOfNotes(simplifiedDiff.NotesCount); if (scores != null) { var validEntries = scores.Where(x => x._score != 0 && (x._playerName == playerName || playerName == null)); if (validEntries.Count() > 0) { var validRanks = validEntries.Select(x => RankModel.GetRankForScore(x._score, x._score, maxRawScore, maxRawScore)); highestRank = (RankModel.Rank)Math.Max((int)(highestRank ?? RankModel.Rank.E), (int)validRanks.Max()); } } } } } return(highestRank); }
public void Setup() => Schedule(() => { Child = details = new BeatmapDetails { RelativeSizeAxes = Axes.Both, Padding = new MarginPadding(150), }; });
/// <summary> /// Get the BeatmapDetails associated with a specific levelID from data retrieved by SongDataCore.BeatSaver. /// </summary> /// <param name="levelID">The level ID associated with the song.</param> /// <param name="beatmapDetails">The returned BeatmapDetails object, or null if it could not be retrieved.</param> /// <returns>True, if a valid BeatmapDetails object was returned. Otherwise, false.</returns> public static bool GetBeatmapDetails(string levelID, out BeatmapDetails beatmapDetails) { if (!ModLoaded) { beatmapDetails = null; return(false); } return(_GetBeatmapDetails(levelID, out beatmapDetails)); }
public void FilterSongList(ref List <BeatmapDetails> detailsList) { // don't need to check _isApplied, that's done outside of this module if ((!_isInitialized) || (!_minEnabledAppliedValue && !_maxEnabledAppliedValue) || (!_difficultiesAppliedValue.Aggregate((x, y) => x || y))) { return; } for (int i = 0; i < detailsList.Count;) { BeatmapDetails details = detailsList[i]; // don't filter out OST beatmaps if (details.IsOST) { ++i; continue; } SimplifiedDifficultyBeatmapSet[] difficultySets = details.DifficultyBeatmapSets; bool remove = false; for (int j = 0; j < 5; ++j) { if (!_difficultiesAppliedValue[j]) { continue; } remove = difficultySets.Count(delegate(SimplifiedDifficultyBeatmapSet difficultySet) { return(difficultySet.DifficultyBeatmaps.Count(delegate(SimplifiedDifficultyBeatmap difficulty) { return difficulty.Difficulty.ToString() == DifficultyStrings[j] && (difficulty.NoteJumpMovementSpeed <_minAppliedValue || difficulty.NoteJumpMovementSpeed> _maxAppliedValue); }) > 0); }) > 0; if (remove) { break; } } if (remove) { detailsList.RemoveAt(i); } else { ++i; } } }
public TestCaseBeatmapDetails() { BeatmapDetails details; Add(details = new BeatmapDetails { RelativeSizeAxes = Axes.Both, Padding = new MarginPadding(150), }); AddStep("all metrics", () => details.Beatmap = new BeatmapInfo { Version = "All Metrics", Metadata = new BeatmapMetadata { Source = "osu!lazer", Tags = "this beatmap has all the metrics", }, BaseDifficulty = new BeatmapDifficulty { CircleSize = 7, DrainRate = 1, OverallDifficulty = 5.7f, ApproachRate = 3.5f, }, StarDifficulty = 5.3f, Metrics = new BeatmapMetrics { Ratings = Enumerable.Range(0, 11), Fails = Enumerable.Range(1, 100).Select(i => i % 12 - 6), Retries = Enumerable.Range(-2, 100).Select(i => i % 12 - 6), }, }); AddStep("all except source", () => details.Beatmap = new BeatmapInfo { Version = "All Metrics", Metadata = new BeatmapMetadata { Tags = "this beatmap has all the metrics", }, BaseDifficulty = new BeatmapDifficulty { CircleSize = 7, DrainRate = 1, OverallDifficulty = 5.7f, ApproachRate = 3.5f, }, StarDifficulty = 5.3f, Metrics = new BeatmapMetrics { Ratings = Enumerable.Range(0, 11), Fails = Enumerable.Range(1, 100).Select(i => i % 12 - 6), Retries = Enumerable.Range(-2, 100).Select(i => i % 12 - 6), }, }); AddStep("ratings", () => details.Beatmap = new BeatmapInfo { Version = "Only Ratings", Metadata = new BeatmapMetadata { Source = "osu!lazer", Tags = "this beatmap has ratings metrics but not retries or fails", }, BaseDifficulty = new BeatmapDifficulty { CircleSize = 6, DrainRate = 9, OverallDifficulty = 6, ApproachRate = 6, }, StarDifficulty = 4.8f, Metrics = new BeatmapMetrics { Ratings = Enumerable.Range(0, 11), }, }); AddStep("fails retries", () => details.Beatmap = new BeatmapInfo { Version = "Only Retries and Fails", Metadata = new BeatmapMetadata { Source = "osu!lazer", Tags = "this beatmap has retries and fails but no ratings", }, BaseDifficulty = new BeatmapDifficulty { CircleSize = 3.7f, DrainRate = 6, OverallDifficulty = 6, ApproachRate = 7, }, StarDifficulty = 2.91f, Metrics = new BeatmapMetrics { Fails = Enumerable.Range(1, 100).Select(i => i % 12 - 6), Retries = Enumerable.Range(-2, 100).Select(i => i % 12 - 6), }, }); AddStep("no metrics", () => details.Beatmap = new BeatmapInfo { Version = "No Metrics", Metadata = new BeatmapMetadata { Source = "osu!lazer", Tags = "this beatmap has no metrics", }, BaseDifficulty = new BeatmapDifficulty { CircleSize = 5, DrainRate = 5, OverallDifficulty = 5.5f, ApproachRate = 6.5f, }, StarDifficulty = 1.97f, Metrics = new BeatmapMetrics(), }); AddStep("null beatmap", () => details.Beatmap = null); }
private static bool _GetBeatmapDetails(string levelID, out BeatmapDetails beatmapDetails) { if (!SongDataCorePlugin.BeatSaver.IsDataAvailable() || !SongDataCorePlugin.BeatSaver.Data.Songs.TryGetValue(levelID.Substring(13).ToLower(), out var song)) { beatmapDetails = null; return(false); } try { string songName = song.metadata.songName; float bpm = float.Parse(song.metadata.bpm); // NOTE: since BeatSaver calculates the duration of a song using the last note (or event?) of a beatmap, instead of the using the length of the audio file, // it is extremely likely that this duration is going to be a bit shorter than the actual length of the audio (typically < 10 seconds shorter) // (or even vastly shorter if there is a long period of no notes at the end of a beatmap) // despite that, i'll keep this limitation since the difference should usually be minimal // and the speedup compared to loading beatmap details for the first time is fairly massive float duration = song.metadata.characteristics.First().Value.difficulties.Select(x => x.Value == null ? 0f : float.Parse(x.Value.length)).Max(); SimplifiedDifficultyBeatmapSet[] difficultyBeatmapSets = song.metadata.characteristics.Select(delegate(KeyValuePair <string, BeatSaverSongCharacteristics> characteristicSetPair) { string loadedCharacteristicName = characteristicSetPair.Value.name.ToLower(); string actualCharacteristicName = null; if (loadedCharacteristicName == "standard") { actualCharacteristicName = "Standard"; } else if (loadedCharacteristicName == "onesaber") { actualCharacteristicName = "OneSaber"; } else if (loadedCharacteristicName == "noarrows") { actualCharacteristicName = "NoArrows"; } else { actualCharacteristicName = characteristicSetPair.Value.name; // currently, only the 'Lightshow' and 'Lawless' custom characteristics should be possible } if (string.IsNullOrEmpty(actualCharacteristicName)) { Logger.log.Debug($"Unable to create SimplifiedDifficultyBeatmapSet from BeatSaver data: could not parse '{(loadedCharacteristicName == null ? "null" : loadedCharacteristicName)}' as a characteristic."); return(new SimplifiedDifficultyBeatmapSet(null, null)); } SimplifiedDifficultyBeatmap[] difficultyBeatmaps = characteristicSetPair.Value.difficulties.Where(x => x.Value != null).Select(delegate(KeyValuePair <string, BeatSaverSongCharacteristicData> characteristicDataPair) { var difficultyName = string.Concat(characteristicDataPair.Key.First().ToString().ToUpper(), string.Concat(characteristicDataPair.Key.Skip(1))); var data = characteristicDataPair.Value; // NOTE: this will may need to be changed in the future, if the 360 notes thing is ever added to PCVR Beat Saber // also, from my testing, the parsed NJS could be 0, so that should be fixed by loading the details stored locally return(new SimplifiedDifficultyBeatmap(difficultyName, float.Parse(data.njs), int.Parse(data.notes), int.Parse(data.bombs), int.Parse(data.obstacles), 0)); }).ToArray(); return(new SimplifiedDifficultyBeatmapSet(actualCharacteristicName, difficultyBeatmaps)); }).ToArray(); // if there were any errors during the creation of the SimplifiedDifficultyBeatmapSet objects, do not create a BeatmapDetails object from it if (difficultyBeatmapSets.Any(x => string.IsNullOrEmpty(x.CharacteristicName) || x.DifficultyBeatmaps == null)) { Logger.log.Warn("Error occurred when attempting to create SimplifiedDifficultyBeatmapSet array from object stored in SongDataCore (could not create BeatmapDetails object)."); beatmapDetails = null; return(false); } beatmapDetails = new BeatmapDetails(levelID, songName, bpm, duration, difficultyBeatmapSets); return(true); } catch (Exception e) { Logger.log.Warn("Error occurred when attempting to create BeatmapDetails object from information stored in SongDataCore"); Logger.log.Warn(e); beatmapDetails = null; return(false); } }
private static SongDataCoreDataStatus _GetBeatmapDetails(CustomPreviewBeatmapLevel level, out BeatmapDetails beatmapDetails) { if (!IsDataAvailable || !SongDataCorePlugin.Songs.Data.Songs.TryGetValue(GetCustomLevelHash(level), out var song)) { beatmapDetails = null; return(SongDataCoreDataStatus.NoData); } try { float bpm = song.bpm; if (bpm < 0.001f) { beatmapDetails = null; return(SongDataCoreDataStatus.InvalidBPM); } // NOTE: since BeatSaver calculates the duration of a song using the last note (or event?) of a beatmap, instead of the using the length of the audio file, // it is extremely likely that this duration is going to be a bit shorter than the actual length of the audio (typically < 10 seconds shorter) // (or even vastly shorter if there is a long period of no notes at the end of a beatmap) // despite that, i'll keep this limitation since the difference should usually be minimal // and the speedup compared to loading beatmap details for the first time is fairly massive Func <KeyValuePair <BeatStarCharacteristics, Dictionary <string, BeatStarSongDifficultyStats> >, float> getDuration = delegate(KeyValuePair <BeatStarCharacteristics, Dictionary <string, BeatStarSongDifficultyStats> > characteristics) { var characteristicDurations = characteristics.Value.Select(delegate(KeyValuePair <string, BeatStarSongDifficultyStats> data) { if (data.Value == null) { return(0f); } else { return(Convert.ToSingle(data.Value.len)); } }); return(characteristicDurations.Any() ? characteristicDurations.Max() : 0f); }; var durations = song.characteristics.Select(getDuration); float duration; if (durations.Any(x => x > 0f)) { // assuming the maximum is the actual duration duration = durations.Max(); } else { beatmapDetails = null; return(SongDataCoreDataStatus.InvalidDuration); } SimplifiedDifficultyBeatmapSet[] difficultyBeatmapSets; try { difficultyBeatmapSets = song.characteristics.Select(delegate(KeyValuePair <BeatStarCharacteristics, Dictionary <string, BeatStarSongDifficultyStats> > characteristicPair) { BeatStarCharacteristics loadedCharacteristicName = characteristicPair.Key; string actualCharacteristicName = loadedCharacteristicName != BeatStarCharacteristics.Unkown ? loadedCharacteristicName.ToString() : null; if (string.IsNullOrEmpty(actualCharacteristicName)) { Logger.log.Debug($"Unable to create SimplifiedDifficultyBeatmapSet from BeatSaver data: could not parse '{loadedCharacteristicName.ToString()}' as a valid characteristic."); return(null); } SimplifiedDifficultyBeatmap[] difficultyBeatmaps = characteristicPair.Value.Where(x => x.Value != null).Select(delegate(KeyValuePair <string, BeatStarSongDifficultyStats> difficultyPair) { // this will throw an exception (that will be caught) if the difficulty name cannot be parsed var diffString = difficultyPair.Key == "Expert+" ? "ExpertPlus" : difficultyPair.Key; var diff = (BeatmapDifficulty)Enum.Parse(typeof(BeatmapDifficulty), diffString); BeatStarSongDifficultyStats data = difficultyPair.Value; // NOTE: from my testing, the parsed NJS could be 0, so that should be fixed by loading the details stored locally return(new SimplifiedDifficultyBeatmap(diff, Convert.ToSingle(data.njs), data.nts, data.bmb, data.obs, 0)); }).ToArray(); return(new SimplifiedDifficultyBeatmapSet(actualCharacteristicName, difficultyBeatmaps)); }).ToArray(); } catch (ArgumentException) { // NOTE: this exception should only be able to be thrown when parsing BeatmapDifficulty, // but that may change if the above function is changed in the future beatmapDetails = null; return(SongDataCoreDataStatus.InvalidDifficultyString); } // if there were any errors during the creation of the SimplifiedDifficultyBeatmapSet objects, do not create a BeatmapDetails object from it // currently, the only error we need to check for here is if the characteristic name is invalid if (difficultyBeatmapSets.Any(x => x == null || string.IsNullOrEmpty(x.CharacteristicName) || x.DifficultyBeatmaps == null)) { beatmapDetails = null; return(SongDataCoreDataStatus.InvalidCharacteristicString); } beatmapDetails = new BeatmapDetails(level.levelID, level.songName, bpm, duration, difficultyBeatmapSets); return(SongDataCoreDataStatus.Success); } catch (Exception e) { Logger.log.Debug($"Exception thrown when trying to create BeatmapDetails object for level ID '{level.levelID}' from information provided by SongDataCore"); Logger.log.Debug(e); beatmapDetails = null; return(SongDataCoreDataStatus.ExceptionThrown); } }
/// <summary> /// Get the BeatmapDetails associated with a specific levelID from data retrieved by SongDataCore.BeatSaver. /// </summary> /// <param name="level">The song's associated preview beatmap level.</param> /// <param name="beatmapDetails">The returned BeatmapDetails object, or null if it could not be retrieved.</param> /// <returns>An enum representing success or what went wrong.</returns> public static SongDataCoreDataStatus GetBeatmapDetails(CustomPreviewBeatmapLevel level, out BeatmapDetails beatmapDetails) { if (!IsModAvailable) { beatmapDetails = null; return(SongDataCoreDataStatus.NoData); } return(_GetBeatmapDetails(level, out beatmapDetails)); }