public void TestMissViaEarlyHit() { var beatmap = new Beatmap { HitObjects = { new HitCircle { Position = new Vector2(256, 192) } } }; var hitWindows = new OsuHitWindows(); hitWindows.SetDifficulty(beatmap.Difficulty.OverallDifficulty); CreateModTest(new ModTestData { Autoplay = false, Mod = new TestAutoMod(), Beatmap = new Beatmap { HitObjects = { new HitCircle { Position = new Vector2(256, 192) } } }, PassCondition = () => Player.Results.Count > 0 && Player.Results[0].TimeOffset < -hitWindows.WindowFor(HitResult.Meh) && !Player.Results[0].IsHit }); }
public OsuPlayfield() { InternalChildren = new Drawable[] { playfieldBorder = new PlayfieldBorder { RelativeSizeAxes = Axes.Both, Depth = 3 }, spinnerProxies = new ProxyContainer { RelativeSizeAxes = Axes.Both }, followPoints = new FollowPointRenderer { RelativeSizeAxes = Axes.Both, Depth = 2, }, judgementLayer = new JudgementContainer <DrawableOsuJudgement> { RelativeSizeAxes = Axes.Both, Depth = 1, }, // Todo: This should not exist, but currently helps to reduce LOH allocations due to unbinding skin source events on judgement disposal // Todo: Remove when hitobjects are properly pooled new SkinProvidingContainer(null) { Child = HitObjectContainer, }, approachCircles = new ProxyContainer { RelativeSizeAxes = Axes.Both, Depth = -1, }, }; hitPolicy = new OrderedHitPolicy(HitObjectContainer); CheckHittable = hitPolicy.IsHittable; var hitWindows = new OsuHitWindows(); foreach (var result in Enum.GetValues(typeof(HitResult)).OfType <HitResult>().Where(r => r > HitResult.None && hitWindows.IsHitResultAllowed(r))) { poolDictionary.Add(result, new DrawableJudgementPool(result)); } AddRangeInternal(poolDictionary.Values); NewResult += onNewResult; }
protected override Skill[] CreateSkills(IBeatmap beatmap, Mod[] mods, double clockRate) { HitWindows hitWindows = new OsuHitWindows(); hitWindows.SetDifficulty(beatmap.Difficulty.OverallDifficulty); hitWindowGreat = hitWindows.WindowFor(HitResult.Great) / clockRate; return(new Skill[] { new Aim(mods), new Speed(mods, hitWindowGreat), new Flashlight(mods) }); }
protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beatmap, Mod[] mods, Skill[] skills, double clockRate) { if (beatmap.HitObjects.Count == 0) { return new OsuDifficultyAttributes { Mods = mods, Skills = skills } } ; double aimRating = Math.Sqrt(skills[0].DifficultyValue()) * difficulty_multiplier; double speedRating = Math.Sqrt(skills[1].DifficultyValue()) * difficulty_multiplier; double baseAimPerformance = Math.Pow(5 * Math.Max(1, aimRating / 0.0675) - 4, 3) / 100000; double baseSpeedPerformance = Math.Pow(5 * Math.Max(1, speedRating / 0.0675) - 4, 3) / 100000; double basePerformance = Math.Pow(Math.Pow(baseAimPerformance, 1.1) + Math.Pow(baseSpeedPerformance, 1.1), 1 / 1.1); double starRating = basePerformance > 0.00001 ? Math.Cbrt(1.12) * 0.027 * (Math.Cbrt(100000 / Math.Pow(2, 1 / 1.1) * basePerformance) + 4) : 0; 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 }); }
protected override Skill[] CreateSkills(IBeatmap beatmap, Mod[] mods, double clockRate) { 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 hitWindowGreat = (int)(hitWindows.WindowFor(HitResult.Great)) / clockRate; return(new Skill[] { new Aim(mods), new Speed(mods, hitWindowGreat), new Flashlight(mods) }); }
protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beatmap, Mod[] mods, Skill[] skills, double clockRate, double upTo = double.PositiveInfinity) { var hitObjects = double.IsPositiveInfinity(upTo) ? beatmap.HitObjects : beatmap.HitObjects.Where(h => h.StartTime <= upTo).ToList(); if (hitObjects.Count == 0) { return new OsuDifficultyAttributes { Mods = mods, Skills = skills } } ; 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; 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 = 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 += hitObjects.OfType <Slider>().Sum(s => s.NestedHitObjects.Count - 1); int hitCircles = hitObjects.OfType <HitCircle>().Count(); 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, CountHitCircles = hitCircles, Skills = skills }); }
public void TestMissViaNotHitting() { var beatmap = new Beatmap { HitObjects = { new HitCircle { Position = new Vector2(256, 192) } } }; var hitWindows = new OsuHitWindows(); hitWindows.SetDifficulty(beatmap.BeatmapInfo.BaseDifficulty.OverallDifficulty); CreateModTest(new ModTestData { Autoplay = false, Beatmap = beatmap, PassCondition = () => Player.Results.Count > 0 && Player.Results[0].TimeOffset >= hitWindows.WindowFor(HitResult.Meh) && Player.Results[0].Type == HitResult.Miss }); }
public OsuPlayfield() { Anchor = Anchor.Centre; Origin = Anchor.Centre; InternalChildren = new Drawable[] { playfieldBorder = new PlayfieldBorder { RelativeSizeAxes = Axes.Both }, spinnerProxies = new ProxyContainer { RelativeSizeAxes = Axes.Both }, FollowPoints = new FollowPointRenderer { RelativeSizeAxes = Axes.Both }, judgementLayer = new JudgementContainer <DrawableOsuJudgement> { RelativeSizeAxes = Axes.Both }, HitObjectContainer, judgementAboveHitObjectLayer = new Container { RelativeSizeAxes = Axes.Both }, approachCircles = new ProxyContainer { RelativeSizeAxes = Axes.Both }, }; HitPolicy = new StartTimeOrderedHitPolicy(); var hitWindows = new OsuHitWindows(); foreach (var result in Enum.GetValues(typeof(HitResult)).OfType <HitResult>().Where(r => r > HitResult.None && hitWindows.IsHitResultAllowed(r))) { poolDictionary.Add(result, new DrawableJudgementPool(result, onJudgementLoaded)); } AddRangeInternal(poolDictionary.Values); NewResult += onNewResult; }
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 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 }); }
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 }); }