diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs index 3c0260f5f5..77094f928b 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs @@ -123,7 +123,7 @@ protected override void CheckForResult(bool userTriggered, double timeOffset) return; } - var result = HitObject.HitWindows.ResultFor(timeOffset); + var result = ResultFor(timeOffset); if (result == HitResult.None || CheckHittable?.Invoke(this, Time.Current) == false) { @@ -146,6 +146,13 @@ protected override void CheckForResult(bool userTriggered, double timeOffset) }); } + /// + /// Retrieves the for a time offset. + /// + /// The time offset. + /// The hit result, or if doesn't result in a judgement. + protected virtual HitResult ResultFor(double timeOffset) => HitObject.HitWindows.ResultFor(timeOffset); + protected override void UpdateInitialTransforms() { base.UpdateInitialTransforms(); diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs index 511cbc2347..7061ce59d0 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs @@ -15,6 +15,7 @@ using osu.Game.Rulesets.Osu.Skinning; using osu.Game.Rulesets.Osu.Skinning.Default; using osu.Game.Rulesets.Osu.UI; +using osu.Game.Rulesets.Scoring; using osuTK.Graphics; using osu.Game.Skinning; @@ -249,7 +250,28 @@ protected override void CheckForResult(bool userTriggered, double timeOffset) if (userTriggered || Time.Current < HitObject.EndTime) return; - ApplyResult(r => r.Type = NestedHitObjects.Any(h => h.Result.IsHit) ? r.Judgement.MaxResult : r.Judgement.MinResult); + if (HitObject.IgnoreJudgement) + { + ApplyResult(r => r.Type = NestedHitObjects.Any(h => h.Result.IsHit) ? r.Judgement.MaxResult : r.Judgement.MinResult); + return; + } + + // If not ignoring judgement, score proportionally based on the number of ticks hit, counting the head circle as a tick. + ApplyResult(r => + { + int totalTicks = NestedHitObjects.Count; + int hitTicks = NestedHitObjects.Count(h => h.IsHit); + double hitFraction = (double)totalTicks / hitTicks; + + if (hitTicks == totalTicks) + r.Type = HitResult.Great; + else if (hitFraction >= 0.5) + r.Type = HitResult.Ok; + else if (hitFraction > 0) + r.Type = HitResult.Meh; + else + r.Type = HitResult.Miss; + }); } 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 c051a9918d..08e9c5eb14 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs @@ -7,6 +7,7 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Game.Rulesets.Objects.Types; +using osu.Game.Rulesets.Scoring; namespace osu.Game.Rulesets.Osu.Objects.Drawables { @@ -19,6 +20,8 @@ public class DrawableSliderHead : DrawableHitCircle protected DrawableSlider DrawableSlider => (DrawableSlider)ParentHitObject; + public override bool DisplayResult => HitObject?.JudgeAsNormalHitCircle ?? base.DisplayResult; + private readonly IBindable pathVersion = new Bindable(); protected override OsuSkinComponents CirclePieceComponent => OsuSkinComponents.SliderHeadHitCircle; @@ -73,6 +76,18 @@ protected override void Update() } } + protected override HitResult ResultFor(double timeOffset) + { + Debug.Assert(HitObject != null); + + if (HitObject.JudgeAsNormalHitCircle) + return base.ResultFor(timeOffset); + + // If not judged as a normal hitcircle, only track whether a hit has occurred (via IgnoreHit) rather than a scorable hit result. + var result = base.ResultFor(timeOffset); + return result.IsHit() ? HitResult.IgnoreHit : result; + } + public Action OnShake; public override void Shake(double maximumLength) => OnShake?.Invoke(maximumLength); diff --git a/osu.Game.Rulesets.Osu/Objects/Slider.cs b/osu.Game.Rulesets.Osu/Objects/Slider.cs index 1670df24a8..e3365a8ccf 100644 --- a/osu.Game.Rulesets.Osu/Objects/Slider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Slider.cs @@ -114,6 +114,12 @@ public int RepeatCount /// public double TickDistanceMultiplier = 1; + /// + /// Whether this 's judgement should be ignored. + /// If false, this will be judged proportionally to the number of ticks hit. + /// + public bool IgnoreJudgement = true; + [JsonIgnore] public HitCircle HeadCircle { get; protected set; } @@ -233,7 +239,7 @@ private void updateNestedSamples() HeadCircle.Samples = this.GetNodeSamples(0); } - public override Judgement CreateJudgement() => new OsuIgnoreJudgement(); + public override Judgement CreateJudgement() => IgnoreJudgement ? new OsuIgnoreJudgement() : new OsuJudgement(); 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 5fc480883a..13eac60300 100644 --- a/osu.Game.Rulesets.Osu/Objects/SliderHeadCircle.cs +++ b/osu.Game.Rulesets.Osu/Objects/SliderHeadCircle.cs @@ -1,13 +1,25 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Osu.Judgements; + namespace osu.Game.Rulesets.Osu.Objects { public class SliderHeadCircle : HitCircle { /// - /// Makes the head circle track the follow circle when the start time is reached. + /// Makes this track the follow circle when the start time is reached. + /// If false, this will be pinned to its initial position in the slider. /// public bool TrackFollowCircle = true; + + /// + /// Whether to treat this as a normal for judgement purposes. + /// If false, judgement will be ignored. + /// + public bool JudgeAsNormalHitCircle = true; + + public override Judgement CreateJudgement() => JudgeAsNormalHitCircle ? base.CreateJudgement() : new OsuIgnoreJudgement(); } }