From cb4568c4a1e7d12fea7dda4a643878e9c2bae675 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 16 Nov 2023 20:21:11 +0900 Subject: [PATCH] Fix first object after break not starting a new combo --- .../Formats/LegacyBeatmapDecoderTest.cs | 15 ++++++++++++ .../TestSceneHitObjectAccentColour.cs | 3 --- .../Resources/break-between-objects.osu | 15 ++++++++++++ .../Beatmaps/Formats/LegacyBeatmapDecoder.cs | 23 +++++++++++++++++++ .../Objects/Legacy/Catch/ConvertHit.cs | 6 +---- .../Objects/Legacy/Catch/ConvertSlider.cs | 6 +---- .../Objects/Legacy/Catch/ConvertSpinner.cs | 6 +---- .../Objects/Legacy/ConvertHitObject.cs | 7 +++++- .../Rulesets/Objects/Legacy/Osu/ConvertHit.cs | 6 +---- .../Objects/Legacy/Osu/ConvertSlider.cs | 6 +---- .../Objects/Legacy/Osu/ConvertSpinner.cs | 6 +---- 11 files changed, 65 insertions(+), 34 deletions(-) create mode 100644 osu.Game.Tests/Resources/break-between-objects.osu diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs index 66151a51e6..be1993957f 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs @@ -1093,5 +1093,20 @@ namespace osu.Game.Tests.Beatmaps.Formats Assert.That(hitObject.Samples.Select(s => s.Volume), Has.All.EqualTo(70)); } } + + [Test] + public void TestNewComboAfterBreak() + { + var decoder = new LegacyBeatmapDecoder { ApplyOffsets = false }; + + using (var resStream = TestResources.OpenResource("break-between-objects.osu")) + using (var stream = new LineBufferedReader(resStream)) + { + var beatmap = decoder.Decode(stream); + Assert.That(((IHasCombo)beatmap.HitObjects[0]).NewCombo, Is.True); + Assert.That(((IHasCombo)beatmap.HitObjects[1]).NewCombo, Is.True); + Assert.That(((IHasCombo)beatmap.HitObjects[2]).NewCombo, Is.False); + } + } } } diff --git a/osu.Game.Tests/Gameplay/TestSceneHitObjectAccentColour.cs b/osu.Game.Tests/Gameplay/TestSceneHitObjectAccentColour.cs index f38c2c9416..acb14f86fc 100644 --- a/osu.Game.Tests/Gameplay/TestSceneHitObjectAccentColour.cs +++ b/osu.Game.Tests/Gameplay/TestSceneHitObjectAccentColour.cs @@ -94,9 +94,6 @@ namespace osu.Game.Tests.Gameplay private class TestHitObjectWithCombo : ConvertHitObject, IHasComboInformation { - public bool NewCombo { get; set; } - public int ComboOffset => 0; - public Bindable IndexInCurrentComboBindable { get; } = new Bindable(); public int IndexInCurrentCombo diff --git a/osu.Game.Tests/Resources/break-between-objects.osu b/osu.Game.Tests/Resources/break-between-objects.osu new file mode 100644 index 0000000000..91821e2c58 --- /dev/null +++ b/osu.Game.Tests/Resources/break-between-objects.osu @@ -0,0 +1,15 @@ +osu file format v14 + +[General] +Mode: 0 + +[Events] +2,200,1200 + +[TimingPoints] +0,307.692307692308,4,2,1,60,1,0 + +[HitObjects] +142,99,0,1,0,0:0:0:0: +323,88,3000,1,0,0:0:0:0: +323,88,4000,1,0,0:0:0:0: diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs index 8c5e4971d5..1ee4670ae2 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs @@ -93,6 +93,8 @@ namespace osu.Game.Beatmaps.Formats // The parsing order of hitobjects matters in mania difficulty calculation this.beatmap.HitObjects = this.beatmap.HitObjects.OrderBy(h => h.StartTime).ToList(); + postProcessBreaks(this.beatmap); + foreach (var hitObject in this.beatmap.HitObjects) { applyDefaults(hitObject); @@ -100,6 +102,27 @@ namespace osu.Game.Beatmaps.Formats } } + /// + /// Processes the beatmap such that a new combo is started the first hitobject following each break. + /// + private void postProcessBreaks(Beatmap beatmap) + { + int currentBreak = 0; + bool forceNewCombo = false; + + foreach (var h in beatmap.HitObjects.OfType()) + { + while (currentBreak < beatmap.Breaks.Count && beatmap.Breaks[currentBreak].EndTime < h.StartTime) + { + forceNewCombo = true; + currentBreak++; + } + + h.NewCombo |= forceNewCombo; + forceNewCombo = false; + } + } + private void applyDefaults(HitObject hitObject) { DifficultyControlPoint difficultyControlPoint = (beatmap.ControlPointInfo as LegacyControlPointInfo)?.DifficultyPointAt(hitObject.StartTime) ?? DifficultyControlPoint.DEFAULT; diff --git a/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertHit.cs b/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertHit.cs index 12b4812824..96c779e79b 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertHit.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertHit.cs @@ -9,16 +9,12 @@ namespace osu.Game.Rulesets.Objects.Legacy.Catch /// /// Legacy osu!catch Hit-type, used for parsing Beatmaps. /// - internal sealed class ConvertHit : ConvertHitObject, IHasPosition, IHasCombo + internal sealed class ConvertHit : ConvertHitObject, IHasPosition { public float X => Position.X; public float Y => Position.Y; public Vector2 Position { get; set; } - - public bool NewCombo { get; set; } - - public int ComboOffset { get; set; } } } diff --git a/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertSlider.cs b/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertSlider.cs index fb1afed3b4..bcf1c7fae2 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertSlider.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertSlider.cs @@ -9,16 +9,12 @@ namespace osu.Game.Rulesets.Objects.Legacy.Catch /// /// Legacy osu!catch Slider-type, used for parsing Beatmaps. /// - internal sealed class ConvertSlider : Legacy.ConvertSlider, IHasPosition, IHasCombo + internal sealed class ConvertSlider : Legacy.ConvertSlider, IHasPosition { public float X => Position.X; public float Y => Position.Y; public Vector2 Position { get; set; } - - public bool NewCombo { get; set; } - - public int ComboOffset { get; set; } } } diff --git a/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertSpinner.cs b/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertSpinner.cs index 014494ec54..5ef3d51cb3 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertSpinner.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertSpinner.cs @@ -8,16 +8,12 @@ namespace osu.Game.Rulesets.Objects.Legacy.Catch /// /// Legacy osu!catch Spinner-type, used for parsing Beatmaps. /// - internal sealed class ConvertSpinner : ConvertHitObject, IHasDuration, IHasXPosition, IHasCombo + internal sealed class ConvertSpinner : ConvertHitObject, IHasDuration, IHasXPosition { public double EndTime => StartTime + Duration; public double Duration { get; set; } public float X => 256; // Required for CatchBeatmapConverter - - public bool NewCombo { get; set; } - - public int ComboOffset { get; set; } } } diff --git a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObject.cs b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObject.cs index 54dbd28c76..bb36aab0b3 100644 --- a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObject.cs +++ b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObject.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Scoring; namespace osu.Game.Rulesets.Objects.Legacy @@ -9,8 +10,12 @@ namespace osu.Game.Rulesets.Objects.Legacy /// /// A hit object only used for conversion, not actual gameplay. /// - internal abstract class ConvertHitObject : HitObject + internal abstract class ConvertHitObject : HitObject, IHasCombo { + public bool NewCombo { get; set; } + + public int ComboOffset { get; set; } + public override Judgement CreateJudgement() => new IgnoreJudgement(); protected override HitWindows CreateHitWindows() => HitWindows.Empty; diff --git a/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertHit.cs b/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertHit.cs index 069366bad3..b7cd4b0dcc 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertHit.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertHit.cs @@ -9,16 +9,12 @@ namespace osu.Game.Rulesets.Objects.Legacy.Osu /// /// Legacy osu! Hit-type, used for parsing Beatmaps. /// - internal sealed class ConvertHit : ConvertHitObject, IHasPosition, IHasCombo + internal sealed class ConvertHit : ConvertHitObject, IHasPosition { public Vector2 Position { get; set; } public float X => Position.X; public float Y => Position.Y; - - public bool NewCombo { get; set; } - - public int ComboOffset { get; set; } } } diff --git a/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertSlider.cs b/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertSlider.cs index 790af6cfc1..8c37154f95 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertSlider.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertSlider.cs @@ -9,7 +9,7 @@ namespace osu.Game.Rulesets.Objects.Legacy.Osu /// /// Legacy osu! Slider-type, used for parsing Beatmaps. /// - internal sealed class ConvertSlider : Legacy.ConvertSlider, IHasPosition, IHasCombo, IHasGenerateTicks + internal sealed class ConvertSlider : Legacy.ConvertSlider, IHasPosition, IHasGenerateTicks { public Vector2 Position { get; set; } @@ -17,10 +17,6 @@ namespace osu.Game.Rulesets.Objects.Legacy.Osu public float Y => Position.Y; - public bool NewCombo { get; set; } - - public int ComboOffset { get; set; } - public bool GenerateTicks { get; set; } = true; } } diff --git a/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertSpinner.cs b/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertSpinner.cs index e9e5ca8c94..d6e24b6bbf 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertSpinner.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertSpinner.cs @@ -9,7 +9,7 @@ namespace osu.Game.Rulesets.Objects.Legacy.Osu /// /// Legacy osu! Spinner-type, used for parsing Beatmaps. /// - internal sealed class ConvertSpinner : ConvertHitObject, IHasDuration, IHasPosition, IHasCombo + internal sealed class ConvertSpinner : ConvertHitObject, IHasDuration, IHasPosition { public double Duration { get; set; } @@ -20,9 +20,5 @@ namespace osu.Game.Rulesets.Objects.Legacy.Osu public float X => Position.X; public float Y => Position.Y; - - public bool NewCombo { get; set; } - - public int ComboOffset { get; set; } } }