protected override DifficultyAttributes Calculate(IBeatmap beatmap, Mod[] mods, double clockRate) { var hitObjects = beatmap.HitObjects as List <OsuHitObject>; double mapLength = 0; if (beatmap.HitObjects.Count > 0) { mapLength = (beatmap.HitObjects.Last().StartTime - beatmap.HitObjects.First().StartTime) / 1000 / clockRate; } double preemptNoClockRate = BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.ApproachRate, 1800, 1200, 450); var noteDensities = NoteDensity.CalculateNoteDensities(hitObjects, preemptNoClockRate); // Tap var tapAttributes = Tap.CalculateTapAttributes(hitObjects, clockRate); // Finger Control double fingerControlDiff = FingerControl.CalculateFingerControlDiff(hitObjects, clockRate); // Aim var aimAttributes = Aim.CalculateAimAttributes(hitObjects, clockRate, tapAttributes.StrainHistory, noteDensities); double tapSr = tap_multiplier * Math.Pow(tapAttributes.TapDifficulty, sr_exponent); double aimSr = aim_multiplier * Math.Pow(aimAttributes.FcProbabilityThroughput, sr_exponent); double fingerControlSr = finger_control_multiplier * Math.Pow(fingerControlDiff, sr_exponent); double sr = Mean.PowerMean(new[] { tapSr, aimSr, fingerControlSr }, 7) * 1.131; 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); return(new OsuDifficultyAttributes { StarRating = sr, Mods = mods, Length = mapLength, TapSr = tapSr, TapDiff = tapAttributes.TapDifficulty, StreamNoteCount = tapAttributes.StreamNoteCount, MashTapDiff = tapAttributes.MashedTapDifficulty, FingerControlSr = fingerControlSr, FingerControlDiff = fingerControlDiff, AimSr = aimSr, AimDiff = aimAttributes.FcProbabilityThroughput, AimHiddenFactor = aimAttributes.HiddenFactor, ComboTps = aimAttributes.ComboThroughputs, MissTps = aimAttributes.MissThroughputs, MissCounts = aimAttributes.MissCounts, CheeseNoteCount = aimAttributes.CheeseNoteCount, CheeseLevels = aimAttributes.CheeseLevels, CheeseFactors = aimAttributes.CheeseFactors, ApproachRate = preempt > 1200 ? (1800 - preempt) / 120 : (1200 - preempt) / 150 + 5, OverallDifficulty = (80 - hitWindowGreat) / 6, MaxCombo = maxCombo }); }
protected override DifficultyAttributes Calculate(IBeatmap beatmap, Mod[] mods, double clockRate) { var hitObjects = beatmap.HitObjects as List <OsuHitObject>; if (beatmap.HitObjects.Count == 0) { return new OsuDifficultyAttributes { Mods = mods } } ; double mapLength = (beatmap.HitObjects.Last().StartTime - beatmap.HitObjects.First().StartTime) / 1000 / clockRate; // Tap (var tapDiff, var streamNoteCount, var mashLevels, var tapSkills, var strainHistory) = Tap.CalculateTapAttributes(hitObjects, clockRate); // Finger Control double fingerControlDiff = FingerControl.CalculateFingerControlDiff(hitObjects, clockRate); // Aim (var aimDiff, var fcTimeTP, var comboTPs, var missTPs, var missCounts, var cheeseNoteCount, var cheeseLevels, var cheeseFactors, var graphText) = Aim.CalculateAimAttributes(hitObjects, clockRate, strainHistory); // graph for aim string graphFilePath = Path.Combine("cache", $"graph_{beatmap.BeatmapInfo.OnlineBeatmapID}.txt"); File.WriteAllText(graphFilePath, graphText); double tapSR = tapMultiplier * Math.Pow(tapDiff, srExponent); double aimSR = aimMultiplier * Math.Pow(aimDiff, srExponent); double fingerControlSR = fingerControlMultiplier * Math.Pow(fingerControlDiff, srExponent); double sr = Mean.PowerMean(new double[] { tapSR, aimSR, fingerControlSR }, 7) * 1.131; 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.Great / 2) / 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); return(new OsuDifficultyAttributes { StarRating = sr, Mods = mods, Length = mapLength, TapSR = tapSR, TapDiff = tapDiff, StreamNoteCount = streamNoteCount, MashLevels = mashLevels, TapSkills = tapSkills, FingerControlSR = fingerControlSR, FingerControlDiff = fingerControlDiff, AimSR = aimSR, AimDiff = aimDiff, ComboTPs = comboTPs, MissTPs = missTPs, MissCounts = missCounts, CheeseNoteCount = cheeseNoteCount, CheeseLevels = cheeseLevels, CheeseFactors = cheeseFactors, ApproachRate = preempt > 1200 ? (1800 - preempt) / 120 : (1200 - preempt) / 150 + 5, OverallDifficulty = (80 - hitWindowGreat) / 6, MaxCombo = maxCombo }); }