From 9c1f4b552e651a8d163cd7adf17a0c6ff3e49f08 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 2 Nov 2023 17:04:49 +0900 Subject: [PATCH 1/7] Rename and invert flags for slider classic behaviours --- osu.Game.Rulesets.Osu/Mods/OsuModClassic.cs | 4 +- .../Objects/Drawables/DrawableSlider.cs | 47 ++++++++++--------- .../Objects/Drawables/DrawableSliderHead.cs | 13 ++++- osu.Game.Rulesets.Osu/Objects/Slider.cs | 8 +++- .../Objects/SliderHeadCircle.cs | 4 +- 5 files changed, 45 insertions(+), 31 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModClassic.cs b/osu.Game.Rulesets.Osu/Mods/OsuModClassic.cs index 8930b4ad70..c668119db7 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModClassic.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModClassic.cs @@ -41,10 +41,10 @@ namespace osu.Game.Rulesets.Osu.Mods switch (hitObject) { case Slider slider: - slider.OnlyJudgeNestedObjects = !NoSliderHeadAccuracy.Value; + slider.ClassicSliderBehaviour = NoSliderHeadAccuracy.Value; foreach (var head in slider.NestedHitObjects.OfType()) - head.JudgeAsNormalHitCircle = !NoSliderHeadAccuracy.Value; + head.ClassicSliderBehaviour = NoSliderHeadAccuracy.Value; break; } diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs index cdfe888c99..a053c99a53 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs @@ -50,7 +50,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables /// public Container OverlayElementContainer { get; private set; } - public override bool DisplayResult => !HitObject.OnlyJudgeNestedObjects; + public override bool DisplayResult => HitObject.ClassicSliderBehaviour; [CanBeNull] public PlaySliderBody SliderBody => Body.Drawable as PlaySliderBody; @@ -272,30 +272,31 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables if (userTriggered || !TailCircle.Judged || Time.Current < HitObject.EndTime) return; - // If only the nested hitobjects are judged, then the slider's own judgement is ignored for scoring purposes. - // But the slider needs to still be judged with a reasonable hit/miss result for visual purposes (hit/miss transforms, etc). - if (HitObject.OnlyJudgeNestedObjects) + if (HitObject.ClassicSliderBehaviour) { - ApplyResult(r => r.Type = NestedHitObjects.Any(h => h.Result.IsHit) ? r.Judgement.MaxResult : r.Judgement.MinResult); - return; - } - - // Otherwise, if this slider also needs to be judged, apply judgement proportionally to the number of nested hitobjects hit. This is the classic osu!stable scoring. - ApplyResult(r => - { - int totalTicks = NestedHitObjects.Count; - int hitTicks = NestedHitObjects.Count(h => h.IsHit); - - if (hitTicks == totalTicks) - r.Type = HitResult.Great; - else if (hitTicks == 0) - r.Type = HitResult.Miss; - else + // Classic behaviour means a slider is judged proportionally to the number of nested hitobjects hit. This is the classic osu!stable scoring. + ApplyResult(r => { - double hitFraction = (double)hitTicks / totalTicks; - r.Type = hitFraction >= 0.5 ? HitResult.Ok : HitResult.Meh; - } - }); + int totalTicks = NestedHitObjects.Count; + int hitTicks = NestedHitObjects.Count(h => h.IsHit); + + if (hitTicks == totalTicks) + r.Type = HitResult.Great; + else if (hitTicks == 0) + r.Type = HitResult.Miss; + else + { + double hitFraction = (double)hitTicks / totalTicks; + r.Type = hitFraction >= 0.5 ? HitResult.Ok : HitResult.Meh; + } + }); + } + else + { + // If only the nested hitobjects are judged, then the slider's own judgement is ignored for scoring purposes. + // But the slider needs to still be judged with a reasonable hit/miss result for visual purposes (hit/miss transforms, etc). + ApplyResult(r => r.Type = NestedHitObjects.Any(h => h.Result.IsHit) ? r.Judgement.MaxResult : r.Judgement.MinResult); + } } public override void PlaySamples() diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs index be6c322668..d99ea70fbd 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs @@ -16,7 +16,16 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables public DrawableSlider DrawableSlider => (DrawableSlider)ParentHitObject; - public override bool DisplayResult => HitObject?.JudgeAsNormalHitCircle ?? base.DisplayResult; + public override bool DisplayResult + { + get + { + if (HitObject?.ClassicSliderBehaviour == true) + return false; + + return base.DisplayResult; + } + } private readonly IBindable pathVersion = new Bindable(); @@ -56,7 +65,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables { Debug.Assert(HitObject != null); - if (HitObject.JudgeAsNormalHitCircle) + if (!HitObject.ClassicSliderBehaviour) return base.ResultFor(timeOffset); // If not judged as a normal hitcircle, judge as a slider tick instead. This is the classic osu!stable scoring. diff --git a/osu.Game.Rulesets.Osu/Objects/Slider.cs b/osu.Game.Rulesets.Osu/Objects/Slider.cs index 3cb9b96090..bcbed8b17f 100644 --- a/osu.Game.Rulesets.Osu/Objects/Slider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Slider.cs @@ -127,7 +127,7 @@ namespace osu.Game.Rulesets.Osu.Objects /// Whether this 's judgement is fully handled by its nested s. /// If false, this will be judged proportionally to the number of nested s hit. /// - public bool OnlyJudgeNestedObjects = true; + public bool ClassicSliderBehaviour = false; public BindableNumber SliderVelocityMultiplierBindable { get; } = new BindableDouble(1) { @@ -262,7 +262,11 @@ namespace osu.Game.Rulesets.Osu.Objects TailSamples = this.GetNodeSamples(repeatCount + 1); } - public override Judgement CreateJudgement() => OnlyJudgeNestedObjects ? new OsuIgnoreJudgement() : new OsuJudgement(); + public override Judgement CreateJudgement() => ClassicSliderBehaviour + // See logic in `DrawableSlider.CheckForResult()` + ? new OsuJudgement() + // Of note, this creates a combo discrepancy for non-classic-mod sliders (there is no combo increase for tail or slider judgement). + : new OsuIgnoreJudgement(); protected override HitWindows CreateHitWindows() => HitWindows.Empty; } diff --git a/osu.Game.Rulesets.Osu/Objects/SliderHeadCircle.cs b/osu.Game.Rulesets.Osu/Objects/SliderHeadCircle.cs index 73c222653e..4712d61dfe 100644 --- a/osu.Game.Rulesets.Osu/Objects/SliderHeadCircle.cs +++ b/osu.Game.Rulesets.Osu/Objects/SliderHeadCircle.cs @@ -12,8 +12,8 @@ namespace osu.Game.Rulesets.Osu.Objects /// Whether to treat this as a normal for judgement purposes. /// If false, this will be judged as a instead. /// - public bool JudgeAsNormalHitCircle = true; + public bool ClassicSliderBehaviour; - public override Judgement CreateJudgement() => JudgeAsNormalHitCircle ? base.CreateJudgement() : new SliderTickJudgement(); + public override Judgement CreateJudgement() => ClassicSliderBehaviour ? new SliderTickJudgement() : base.CreateJudgement(); } } From 9af2a5930cf3696ffecea558e94bcec01b642ace Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 2 Nov 2023 17:39:41 +0900 Subject: [PATCH 2/7] Remove redundant passing of `Scale` to nested objects --- osu.Game.Rulesets.Osu/Objects/Slider.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Slider.cs b/osu.Game.Rulesets.Osu/Objects/Slider.cs index bcbed8b17f..0859a6fe17 100644 --- a/osu.Game.Rulesets.Osu/Objects/Slider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Slider.cs @@ -187,7 +187,6 @@ namespace osu.Game.Rulesets.Osu.Objects StartTime = e.Time, Position = Position + Path.PositionAt(e.PathProgress), StackHeight = StackHeight, - Scale = Scale, }); break; @@ -206,7 +205,7 @@ namespace osu.Game.Rulesets.Osu.Objects RepeatIndex = e.SpanIndex, StartTime = e.Time, Position = EndPosition, - StackHeight = StackHeight + StackHeight = StackHeight, }); break; @@ -217,7 +216,6 @@ namespace osu.Game.Rulesets.Osu.Objects StartTime = StartTime + (e.SpanIndex + 1) * SpanDuration, Position = Position + Path.PositionAt(e.PathProgress), StackHeight = StackHeight, - Scale = Scale, }); break; } From a7705284e7b56bcedaee756ec8f37432f029d62c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 2 Nov 2023 18:02:47 +0900 Subject: [PATCH 3/7] Add better commenting around `DrawableSliderHead` logic --- .../Objects/Drawables/DrawableSliderHead.cs | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs index d99ea70fbd..8463c78319 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs @@ -65,12 +65,14 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables { Debug.Assert(HitObject != null); - if (!HitObject.ClassicSliderBehaviour) - return base.ResultFor(timeOffset); - - // If not judged as a normal hitcircle, judge as a slider tick instead. This is the classic osu!stable scoring. - var result = base.ResultFor(timeOffset); - return result.IsHit() ? HitResult.LargeTickHit : HitResult.LargeTickMiss; + return HitObject.ClassicSliderBehaviour + // In classic slider behaviour, heads are considered fully hit if in the largest hit window. + // We can't award a full Great because the true Great judgement is awarded on the Slider itself, + // reduced based on number of ticks hit. + // + // so we use the most suitable LargeTick judgement here instead. + ? base.ResultFor(timeOffset).IsHit() ? HitResult.LargeTickHit : HitResult.LargeTickMiss + : base.ResultFor(timeOffset); } public override void Shake() From bf9f20705f06e3094ae7e772f4ac59fa45472856 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 2 Nov 2023 17:39:23 +0900 Subject: [PATCH 4/7] Simplify classic behaviour flag to only need to be specified on the slider itself --- osu.Game.Rulesets.Osu/Mods/OsuModClassic.cs | 4 ---- osu.Game.Rulesets.Osu/Objects/Slider.cs | 14 +++++++++++++- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModClassic.cs b/osu.Game.Rulesets.Osu/Mods/OsuModClassic.cs index c668119db7..f20f95b384 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModClassic.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModClassic.cs @@ -42,10 +42,6 @@ namespace osu.Game.Rulesets.Osu.Mods { case Slider slider: slider.ClassicSliderBehaviour = NoSliderHeadAccuracy.Value; - - foreach (var head in slider.NestedHitObjects.OfType()) - head.ClassicSliderBehaviour = NoSliderHeadAccuracy.Value; - break; } } diff --git a/osu.Game.Rulesets.Osu/Objects/Slider.cs b/osu.Game.Rulesets.Osu/Objects/Slider.cs index 0859a6fe17..50791183f5 100644 --- a/osu.Game.Rulesets.Osu/Objects/Slider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Slider.cs @@ -127,7 +127,18 @@ namespace osu.Game.Rulesets.Osu.Objects /// Whether this 's judgement is fully handled by its nested s. /// If false, this will be judged proportionally to the number of nested s hit. /// - public bool ClassicSliderBehaviour = false; + public bool ClassicSliderBehaviour + { + get => classicSliderBehaviour; + set + { + classicSliderBehaviour = value; + if (HeadCircle != null) + HeadCircle.ClassicSliderBehaviour = value; + } + } + + private bool classicSliderBehaviour; public BindableNumber SliderVelocityMultiplierBindable { get; } = new BindableDouble(1) { @@ -196,6 +207,7 @@ namespace osu.Game.Rulesets.Osu.Objects StartTime = e.Time, Position = Position, StackHeight = StackHeight, + ClassicSliderBehaviour = ClassicSliderBehaviour, }); break; From ac6fb386d137a13eae242fa65cf417e3d6ec4abb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 2 Nov 2023 23:42:52 +0900 Subject: [PATCH 5/7] Remove nested ternary --- .../Objects/Drawables/DrawableSliderHead.cs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs index 8463c78319..83882481a8 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs @@ -65,14 +65,17 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables { Debug.Assert(HitObject != null); - return HitObject.ClassicSliderBehaviour - // In classic slider behaviour, heads are considered fully hit if in the largest hit window. + if (HitObject.ClassicSliderBehaviour) + { + // With classic slider behaviour, heads are considered fully hit if in the largest hit window. // We can't award a full Great because the true Great judgement is awarded on the Slider itself, // reduced based on number of ticks hit. // // so we use the most suitable LargeTick judgement here instead. - ? base.ResultFor(timeOffset).IsHit() ? HitResult.LargeTickHit : HitResult.LargeTickMiss - : base.ResultFor(timeOffset); + return base.ResultFor(timeOffset).IsHit() ? HitResult.LargeTickHit : HitResult.LargeTickMiss; + } + + return base.ResultFor(timeOffset); } public override void Shake() From 86ede717cbeb705a7036276b49e5c53aeb6ecc53 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 2 Nov 2023 18:52:47 +0100 Subject: [PATCH 6/7] Clean up comment --- osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs index 83882481a8..ff690417a8 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs @@ -69,8 +69,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables { // With classic slider behaviour, heads are considered fully hit if in the largest hit window. // We can't award a full Great because the true Great judgement is awarded on the Slider itself, - // reduced based on number of ticks hit. - // + // reduced based on number of ticks hit, // so we use the most suitable LargeTick judgement here instead. return base.ResultFor(timeOffset).IsHit() ? HitResult.LargeTickHit : HitResult.LargeTickMiss; } From a0757ce13f3156c526d51b7d3be6afcd47436230 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 2 Nov 2023 18:58:14 +0100 Subject: [PATCH 7/7] Update xmldoc to match flipped flag semantics --- osu.Game.Rulesets.Osu/Objects/Slider.cs | 4 ++-- osu.Game.Rulesets.Osu/Objects/SliderHeadCircle.cs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Slider.cs b/osu.Game.Rulesets.Osu/Objects/Slider.cs index 50791183f5..7f2d9592af 100644 --- a/osu.Game.Rulesets.Osu/Objects/Slider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Slider.cs @@ -124,8 +124,8 @@ namespace osu.Game.Rulesets.Osu.Objects public double TickDistanceMultiplier = 1; /// - /// Whether this 's judgement is fully handled by its nested s. - /// If false, this will be judged proportionally to the number of nested s hit. + /// If , 's judgement is fully handled by its nested s. + /// If , this will be judged proportionally to the number of nested s hit. /// public bool ClassicSliderBehaviour { diff --git a/osu.Game.Rulesets.Osu/Objects/SliderHeadCircle.cs b/osu.Game.Rulesets.Osu/Objects/SliderHeadCircle.cs index 4712d61dfe..8305481788 100644 --- a/osu.Game.Rulesets.Osu/Objects/SliderHeadCircle.cs +++ b/osu.Game.Rulesets.Osu/Objects/SliderHeadCircle.cs @@ -9,8 +9,8 @@ namespace osu.Game.Rulesets.Osu.Objects public class SliderHeadCircle : HitCircle { /// - /// Whether to treat this as a normal for judgement purposes. - /// If false, this will be judged as a instead. + /// If , treat this as a normal for judgement purposes. + /// If , this will be judged as a instead. /// public bool ClassicSliderBehaviour;