diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModClassic.cs b/osu.Game.Rulesets.Osu/Mods/OsuModClassic.cs index 8930b4ad70..f20f95b384 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModClassic.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModClassic.cs @@ -41,11 +41,7 @@ namespace osu.Game.Rulesets.Osu.Mods switch (hitObject) { case Slider slider: - slider.OnlyJudgeNestedObjects = !NoSliderHeadAccuracy.Value; - - foreach (var head in slider.NestedHitObjects.OfType()) - head.JudgeAsNormalHitCircle = !NoSliderHeadAccuracy.Value; - + slider.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..ff690417a8 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,12 +65,16 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables { Debug.Assert(HitObject != null); - if (HitObject.JudgeAsNormalHitCircle) - return base.ResultFor(timeOffset); + 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. + return base.ResultFor(timeOffset).IsHit() ? HitResult.LargeTickHit : HitResult.LargeTickMiss; + } - // 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 base.ResultFor(timeOffset); } public override void Shake() diff --git a/osu.Game.Rulesets.Osu/Objects/Slider.cs b/osu.Game.Rulesets.Osu/Objects/Slider.cs index 3cb9b96090..7f2d9592af 100644 --- a/osu.Game.Rulesets.Osu/Objects/Slider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Slider.cs @@ -124,10 +124,21 @@ 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 OnlyJudgeNestedObjects = true; + public bool ClassicSliderBehaviour + { + get => classicSliderBehaviour; + set + { + classicSliderBehaviour = value; + if (HeadCircle != null) + HeadCircle.ClassicSliderBehaviour = value; + } + } + + private bool classicSliderBehaviour; public BindableNumber SliderVelocityMultiplierBindable { get; } = new BindableDouble(1) { @@ -187,7 +198,6 @@ namespace osu.Game.Rulesets.Osu.Objects StartTime = e.Time, Position = Position + Path.PositionAt(e.PathProgress), StackHeight = StackHeight, - Scale = Scale, }); break; @@ -197,6 +207,7 @@ namespace osu.Game.Rulesets.Osu.Objects StartTime = e.Time, Position = Position, StackHeight = StackHeight, + ClassicSliderBehaviour = ClassicSliderBehaviour, }); break; @@ -206,7 +217,7 @@ namespace osu.Game.Rulesets.Osu.Objects RepeatIndex = e.SpanIndex, StartTime = e.Time, Position = EndPosition, - StackHeight = StackHeight + StackHeight = StackHeight, }); break; @@ -217,7 +228,6 @@ namespace osu.Game.Rulesets.Osu.Objects StartTime = StartTime + (e.SpanIndex + 1) * SpanDuration, Position = Position + Path.PositionAt(e.PathProgress), StackHeight = StackHeight, - Scale = Scale, }); break; } @@ -262,7 +272,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..8305481788 100644 --- a/osu.Game.Rulesets.Osu/Objects/SliderHeadCircle.cs +++ b/osu.Game.Rulesets.Osu/Objects/SliderHeadCircle.cs @@ -9,11 +9,11 @@ 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 JudgeAsNormalHitCircle = true; + public bool ClassicSliderBehaviour; - public override Judgement CreateJudgement() => JudgeAsNormalHitCircle ? base.CreateJudgement() : new SliderTickJudgement(); + public override Judgement CreateJudgement() => ClassicSliderBehaviour ? new SliderTickJudgement() : base.CreateJudgement(); } }