Example #1
0
        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);
            }
        }
Example #2
0
        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);
        }
Example #3
0
        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);
        }
Example #5
0
 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;
                }
            }
        }
Example #8
0
        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);
            }
        }
Example #10
0
        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);
            }
        }
Example #11
0
        /// <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));
        }