protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beatmap, Mod[] mods, Skill[] skills, double clockRate) { if (beatmap.HitObjects.Count == 0) { return new OsuDifficultyAttributes { Mods = mods, Skills = skills } } ; List <double> combinedBonuses = calculateCombinedBonuses(skills); int totalHits = beatmap.HitObjects.Count(h => h is HitCircle || h is Slider); Aim aimSkill = skills[0] as Aim; Speed speedSkill = skills[1] as Speed; //minor discrepancy here because the combined bonus is not taken into account for the length value double aimTotal = aimSkill.LengthValue(totalHits) * 2; double speedTotal = speedSkill.LengthValue(totalHits) * 2; aimSkill.AddCombinedCorrection(combinedBonuses); speedSkill.AddCombinedCorrection(combinedBonuses, skills[4]); double aimDifficulty = aimSkill.CombinedDifficultyValue(); double speedDifficulty = speedSkill.CombinedDifficultyValue(); double aimRating = Math.Sqrt(aimDifficulty); double speedRating = Math.Sqrt(speedDifficulty); //consistency double totalAimBonus = Math.Pow(aimSkill.ConsistencyValue(aimDifficulty), 0.7) * 0.045 + calculateMixedAimBonus(skills[2] as SnapAim, skills[3] as FlowAim); double totalSpeedBonus = Math.Pow(speedSkill.ConsistencyValue(speedDifficulty), 0.7) * 0.045; //calculate the SR first to avoid unnecessary inflation, this should pose no problems as it is just a display value double displayAim = aimRating * (1.0 + totalAimBonus) * difficulty_multiplier; double displaySpeed = speedRating * (1.0 + totalSpeedBonus) * difficulty_multiplier; double starRating = displayAim + displaySpeed + Math.Abs(displayAim - displaySpeed) / 2; //length bonus double aimDiffMultiplier = 1.0 + Math.Pow(aimRating, 1.5) / 15000; aimTotal = Math.Pow(aimTotal, aimDiffMultiplier); double aimLengthBonus = 1.0 + 0.36 * Math.Min(1.0, aimTotal / 2000.0) + (aimTotal > 2000 ? Math.Log10(aimTotal / 2000.0) * 0.48 : 0.0); double speedLengthBonus = 1.0 + 0.1 * Math.Min(1.0, speedTotal / 2000.0) + (speedTotal > 2000 ? Math.Log10(speedTotal / 2000.0) * 0.2 : 0.0); totalAimBonus += Math.Pow(aimLengthBonus, 0.33); totalSpeedBonus += Math.Pow(speedLengthBonus, 0.33); aimRating *= totalAimBonus * difficulty_multiplier; speedRating *= totalSpeedBonus * difficulty_multiplier; HitWindows hitWindows = new OsuHitWindows(); hitWindows.SetDifficulty(beatmap.BeatmapInfo.BaseDifficulty.OverallDifficulty); // Todo: These int casts are temporary to achieve 1:1 results with osu!stable, and should be removed in the future double hitWindowGreat = (int)(hitWindows.WindowFor(HitResult.Great)) / clockRate; double preempt = (int)BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.ApproachRate, 1800, 1200, 450) / clockRate; 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 <Slider>().Sum(s => s.NestedHitObjects.Count - 1); int hitCirclesCount = beatmap.HitObjects.Count(h => h is HitCircle); int spinnerCount = beatmap.HitObjects.Count(h => h is Spinner); return(new OsuDifficultyAttributes { StarRating = starRating, Mods = mods, AimStrain = aimRating, SpeedStrain = speedRating, ApproachRate = preempt > 1200 ? (1800 - preempt) / 120 : (1200 - preempt) / 150 + 5, OverallDifficulty = (80 - hitWindowGreat) / 6, MaxCombo = maxCombo, HitCircleCount = hitCirclesCount, SpinnerCount = spinnerCount, Skills = skills }); }