// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. using System; using System.Collections.Generic; using System.Linq; using osu.Game.Beatmaps; using osu.Game.Rulesets.Difficulty; using osu.Game.Rulesets.Difficulty.Preprocessing; using osu.Game.Rulesets.Difficulty.Skills; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu.Difficulty.Preprocessing; using osu.Game.Rulesets.Osu.Difficulty.Skills; using osu.Game.Rulesets.Osu.Mods; using osu.Game.Rulesets.Osu.Objects; namespace osu.Game.Rulesets.Osu.Difficulty { public class OsuDifficultyCalculator : DifficultyCalculator { private const double difficulty_multiplier = 0.0675; public OsuDifficultyCalculator(Ruleset ruleset, WorkingBeatmap beatmap) : base(ruleset, beatmap) { } protected override void PopulateAttributes(DifficultyAttributes attributes, IBeatmap beatmap, Skill[] skills, double timeRate) { var osuAttributes = (OsuDifficultyAttributes)attributes; double aimRating = Math.Sqrt(skills[0].DifficultyValue()) * difficulty_multiplier; double speedRating = Math.Sqrt(skills[1].DifficultyValue()) * difficulty_multiplier; double starRating = aimRating + speedRating + Math.Abs(aimRating - speedRating) / 2; // Todo: These int casts are temporary to achieve 1:1 results with osu!stable, and should be removed in the future double hitWindowGreat = (int)(beatmap.HitObjects.First().HitWindows.Great / 2) / timeRate; double preempt = (int)BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.ApproachRate, 1800, 1200, 450) / timeRate; int maxCombo = beatmap.HitObjects.Count; // Add the ticks + tail of the slider. 1 is subtracted because the head circle would be counted twice (once for the slider itself in the line above) maxCombo += beatmap.HitObjects.OfType().Sum(s => s.NestedHitObjects.Count - 1); osuAttributes.StarRating = starRating; osuAttributes.AimStrain = aimRating; osuAttributes.SpeedStrain = speedRating; osuAttributes.ApproachRate = preempt > 1200 ? (1800 - preempt) / 120 : (1200 - preempt) / 150 + 5; osuAttributes.OverallDifficulty = (80 - hitWindowGreat) / 6; osuAttributes.MaxCombo = maxCombo; } protected override IEnumerable CreateDifficultyHitObjects(IBeatmap beatmap, double timeRate) { // The first jump is formed by the first two hitobjects of the map. // If the map has less than two OsuHitObjects, the enumerator will not return anything. for (int i = 1; i < beatmap.HitObjects.Count; i++) { var lastLast = i > 1 ? beatmap.HitObjects[i - 2] : null; var last = beatmap.HitObjects[i - 1]; var current = beatmap.HitObjects[i]; yield return new OsuDifficultyHitObject(current, lastLast, last, timeRate); } } protected override Skill[] CreateSkills() => new Skill[] { new Aim(), new Speed() }; protected override DifficultyAttributes CreateDifficultyAttributes() => new OsuDifficultyAttributes(); protected override Mod[] DifficultyAdjustmentMods => new Mod[] { new OsuModDoubleTime(), new OsuModHalfTime(), new OsuModEasy(), new OsuModHardRock(), }; } }