From 12a48d2774dd0e4aa19cdd989b34c7022343ff1e Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 19 Mar 2020 19:16:24 +0900 Subject: [PATCH] Cause all earlier hitobjects to get missed --- .../TestSceneNoteLock.cs | 13 ++++- .../Objects/Drawables/DrawableOsuHitObject.cs | 6 +++ osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs | 52 +++++++++++++++++++ 3 files changed, 70 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneNoteLock.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneNoteLock.cs index 59d8727ae1..e2b8364f3e 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneNoteLock.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneNoteLock.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.Linq; using NUnit.Framework; using osu.Framework.Screens; +using osu.Framework.Utils; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Replays; @@ -24,6 +25,8 @@ namespace osu.Game.Rulesets.Osu.Tests { private const double time_first_circle = 1500; private const double time_second_circle = 1600; + private const double early_miss_window = 1000; // time after -1000 to -500 is considered a miss + private const double late_miss_window = 500; // time after +500 is considered a miss private static readonly Vector2 position_first_circle = Vector2.Zero; private static readonly Vector2 position_second_circle = new Vector2(80); @@ -40,6 +43,7 @@ namespace osu.Game.Rulesets.Osu.Tests }); addJudgementAssert(HitResult.Miss, HitResult.Miss); + addJudgementOffsetAssert(late_miss_window); } /// @@ -54,6 +58,7 @@ namespace osu.Game.Rulesets.Osu.Tests }); addJudgementAssert(HitResult.Miss, HitResult.Great); + addJudgementOffsetAssert(0); } /// @@ -68,6 +73,7 @@ namespace osu.Game.Rulesets.Osu.Tests }); addJudgementAssert(HitResult.Miss, HitResult.Great); + addJudgementOffsetAssert(100); } /// @@ -91,6 +97,11 @@ namespace osu.Game.Rulesets.Osu.Tests AddAssert($"second circle judgement is {secondCircle}", () => judgementResults.Single(r => r.HitObject.StartTime == time_second_circle).Type == secondCircle); } + private void addJudgementOffsetAssert(double offset) + { + AddAssert($"first circle judged at {offset}", () => Precision.AlmostEquals(judgementResults.Single(r => r.HitObject.StartTime == time_first_circle).TimeOffset, offset, 100)); + } + private ScoreAccessibleReplayPlayer currentPlayer; private List judgementResults; private bool allJudgedFired; @@ -157,7 +168,7 @@ namespace osu.Game.Rulesets.Osu.Tests private static readonly DifficultyRange[] ranges = { new DifficultyRange(HitResult.Great, 500, 500, 500), - new DifficultyRange(HitResult.Miss, 1000, 1000, 1000), + new DifficultyRange(HitResult.Miss, early_miss_window, early_miss_window, early_miss_window), }; public override bool IsHitResultAllowed(HitResult result) => result == HitResult.Great || result == HitResult.Miss; diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs index 3e66549ca0..13829dc2f7 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs @@ -7,6 +7,7 @@ using osu.Framework.Graphics; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Osu.Judgements; using osu.Game.Graphics.Containers; +using osu.Game.Rulesets.Scoring; namespace osu.Game.Rulesets.Osu.Objects.Drawables { @@ -61,6 +62,11 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables } } + /// + /// Causes this to get missed, disregarding all conditions in implementations of . + /// + public void MissForcefully() => ApplyResult(r => r.Type = HitResult.Miss); + protected override JudgementResult CreateResult(Judgement judgement) => new OsuJudgementResult(HitObject, judgement); } } diff --git a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs index bf91504b00..e36d32d01a 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using osu.Framework.Extensions.IEnumerableExtensions; using osuTK; using osu.Framework.Graphics; @@ -104,6 +105,8 @@ namespace osu.Game.Rulesets.Osu.UI private void onNewResult(DrawableHitObject judgedObject, JudgementResult result) { + missAllEarlier(result); + if (!judgedObject.DisplayResult || !DisplayJudgements.Value) return; @@ -117,6 +120,55 @@ namespace osu.Game.Rulesets.Osu.UI judgementLayer.Add(explosion); } + /// + /// Misses all s occurring earlier than the start time of a judged . + /// + /// The of the judged . + private void missAllEarlier(JudgementResult result) + { + // Hitobjects that count as bonus should not cause other hitobjects to get missed. + // E.g. For the sequence slider-head -> circle -> slider-tick, hitting the tick before the circle should not cause the circle to be missed. + // E.g. For the sequence spinner -> circle -> spinner-bonus, hitting the bonus before the circle should not cause the circle to be missed. + if (result.Judgement.IsBonus) + return; + + // The minimum start time required for hitobjects so that they aren't missed. + double minimumTime = result.HitObject.StartTime; + + foreach (var obj in HitObjectContainer.AliveObjects) + { + if (obj.HitObject.StartTime >= minimumTime) + break; + + attemptMiss(obj); + + foreach (var n in obj.NestedHitObjects) + { + if (n.HitObject.StartTime >= minimumTime) + break; + + attemptMiss(n); + } + } + + static void attemptMiss(DrawableHitObject obj) + { + if (!(obj is DrawableOsuHitObject osuObject)) + throw new InvalidOperationException($"{obj.GetType()} is not a {nameof(DrawableOsuHitObject)}."); + + // Hitobjects that have already been judged cannot be missed. + if (osuObject.Judged) + return; + + // Hitobjects that count as bonus should not be missed. + // For the sequence slider-head -> slider-tick -> circle, hitting the circle before the tick should not cause the tick to be missed. + if (osuObject.Result.Judgement.IsBonus) + return; + + osuObject.MissForcefully(); + } + } + public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => HitObjectContainer.ReceivePositionalInputAt(screenSpacePos); private class ApproachCircleProxyContainer : LifetimeManagementContainer