From cd6d070b4aacc33a880562c928001fdc90dee920 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 5 May 2021 07:27:57 +0300 Subject: [PATCH 01/19] Consider "combo offsets" as legacy logic and separate from combo information --- .../Beatmaps/CatchBeatmapConverter.cs | 3 --- .../Objects/CatchHitObject.cs | 2 -- .../Beatmaps/OsuBeatmapConverter.cs | 6 ++--- osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs | 14 ++++------- .../TestSceneHitObjectAccentColour.cs | 1 - osu.Game/Beatmaps/BeatmapProcessor.cs | 12 +++++++++- .../Beatmaps/Formats/LegacyBeatmapEncoder.cs | 5 ++-- .../Objects/Legacy/Catch/ConvertHit.cs | 2 -- .../Legacy/Catch/ConvertHitObjectParser.cs | 14 ----------- .../Objects/Legacy/Catch/ConvertSlider.cs | 2 -- .../Objects/Legacy/Catch/ConvertSpinner.cs | 2 -- .../Rulesets/Objects/Legacy/Osu/ConvertHit.cs | 6 +++-- .../Legacy/Osu/ConvertHitObjectParser.cs | 4 ++-- .../Objects/Legacy/Osu/ConvertSlider.cs | 6 +++-- .../Objects/Legacy/Osu/ConvertSpinner.cs | 2 -- osu.Game/Rulesets/Objects/Types/IHasCombo.cs | 5 ---- .../Types/IHasLegacyBeatmapComboOffset.cs | 24 +++++++++++++++++++ 17 files changed, 56 insertions(+), 54 deletions(-) create mode 100644 osu.Game/Rulesets/Objects/Types/IHasLegacyBeatmapComboOffset.cs diff --git a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs index 34964fc4ae..8b0213bfeb 100644 --- a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs +++ b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs @@ -38,7 +38,6 @@ namespace osu.Game.Rulesets.Catch.Beatmaps RepeatCount = curveData.RepeatCount, X = positionData?.X ?? 0, NewCombo = comboData?.NewCombo ?? false, - ComboOffset = comboData?.ComboOffset ?? 0, LegacyLastTickOffset = (obj as IHasLegacyLastTickOffset)?.LegacyLastTickOffset ?? 0 }.Yield(); @@ -49,7 +48,6 @@ namespace osu.Game.Rulesets.Catch.Beatmaps Samples = obj.Samples, Duration = endTime.Duration, NewCombo = comboData?.NewCombo ?? false, - ComboOffset = comboData?.ComboOffset ?? 0, }.Yield(); default: @@ -58,7 +56,6 @@ namespace osu.Game.Rulesets.Catch.Beatmaps StartTime = obj.StartTime, Samples = obj.Samples, NewCombo = comboData?.NewCombo ?? false, - ComboOffset = comboData?.ComboOffset ?? 0, X = positionData?.X ?? 0 }.Yield(); } diff --git a/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs b/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs index ae45182960..ce2da314ca 100644 --- a/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs +++ b/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs @@ -67,8 +67,6 @@ namespace osu.Game.Rulesets.Catch.Objects public virtual bool NewCombo { get; set; } - public int ComboOffset { get; set; } - public Bindable IndexInCurrentComboBindable { get; } = new Bindable(); public int IndexInCurrentCombo diff --git a/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapConverter.cs b/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapConverter.cs index a2fc4848af..d812f86938 100644 --- a/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapConverter.cs +++ b/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapConverter.cs @@ -40,7 +40,7 @@ namespace osu.Game.Rulesets.Osu.Beatmaps RepeatCount = curveData.RepeatCount, Position = positionData?.Position ?? Vector2.Zero, NewCombo = comboData?.NewCombo ?? false, - ComboOffset = comboData?.ComboOffset ?? 0, + LegacyBeatmapComboOffset = (original as IHasLegacyBeatmapComboOffset)?.LegacyBeatmapComboOffset ?? 0, LegacyLastTickOffset = (original as IHasLegacyLastTickOffset)?.LegacyLastTickOffset, // prior to v8, speed multipliers don't adjust for how many ticks are generated over the same distance. // this results in more (or less) ticks being generated in /// The radius of hit objects (ie. the radius of a ). @@ -73,14 +73,6 @@ namespace osu.Game.Rulesets.Osu.Objects public virtual bool NewCombo { get; set; } - public readonly Bindable ComboOffsetBindable = new Bindable(); - - public int ComboOffset - { - get => ComboOffsetBindable.Value; - set => ComboOffsetBindable.Value = value; - } - public Bindable IndexInCurrentComboBindable { get; } = new Bindable(); public virtual int IndexInCurrentCombo @@ -105,6 +97,10 @@ namespace osu.Game.Rulesets.Osu.Objects set => LastInComboBindable.Value = value; } + public int LegacyBeatmapComboOffset { get; set; } + + public int LegacyBeatmapComboIndex { get; set; } + protected OsuHitObject() { StackHeightBindable.BindValueChanged(height => diff --git a/osu.Game.Tests/Gameplay/TestSceneHitObjectAccentColour.cs b/osu.Game.Tests/Gameplay/TestSceneHitObjectAccentColour.cs index d08d08390b..99a681acf8 100644 --- a/osu.Game.Tests/Gameplay/TestSceneHitObjectAccentColour.cs +++ b/osu.Game.Tests/Gameplay/TestSceneHitObjectAccentColour.cs @@ -82,7 +82,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(); diff --git a/osu.Game/Beatmaps/BeatmapProcessor.cs b/osu.Game/Beatmaps/BeatmapProcessor.cs index b7b5adc52e..ac2d7d0fc1 100644 --- a/osu.Game/Beatmaps/BeatmapProcessor.cs +++ b/osu.Game/Beatmaps/BeatmapProcessor.cs @@ -37,7 +37,7 @@ namespace osu.Game.Beatmaps if (obj.NewCombo) { obj.IndexInCurrentCombo = 0; - obj.ComboIndex = (lastObj?.ComboIndex ?? 0) + obj.ComboOffset + 1; + obj.ComboIndex = (lastObj?.ComboIndex ?? 0) + 1; if (lastObj != null) lastObj.LastInCombo = true; @@ -48,6 +48,16 @@ namespace osu.Game.Beatmaps obj.ComboIndex = lastObj.ComboIndex; } + if (obj is IHasLegacyBeatmapComboOffset legacyObj) + { + var lastLegacyObj = (IHasLegacyBeatmapComboOffset)lastObj; + + if (obj.NewCombo) + legacyObj.LegacyBeatmapComboIndex = (lastLegacyObj?.LegacyBeatmapComboIndex ?? 0) + legacyObj.LegacyBeatmapComboOffset + 1; + else if (lastLegacyObj != null) + legacyObj.LegacyBeatmapComboIndex = lastLegacyObj.LegacyBeatmapComboIndex; + } + lastObj = obj; } } diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs index acbf57d25f..e494f6c95c 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs @@ -292,10 +292,11 @@ namespace osu.Game.Beatmaps.Formats if (hitObject is IHasCombo combo) { - type = (LegacyHitObjectType)(combo.ComboOffset << 4); - if (combo.NewCombo) type |= LegacyHitObjectType.NewCombo; + + if (hitObject is IHasLegacyBeatmapComboOffset comboOffset) + type |= (LegacyHitObjectType)(comboOffset.LegacyBeatmapComboOffset << 4); } switch (hitObject) diff --git a/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertHit.cs b/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertHit.cs index 19722fb796..f1a03a03e2 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertHit.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertHit.cs @@ -13,7 +13,5 @@ namespace osu.Game.Rulesets.Objects.Legacy.Catch public float X { get; set; } public bool NewCombo { get; set; } - - public int ComboOffset { get; set; } } } diff --git a/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertHitObjectParser.cs index c10c8dc30f..d71ddfd05e 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertHitObjectParser.cs @@ -18,21 +18,16 @@ namespace osu.Game.Rulesets.Objects.Legacy.Catch } private bool forceNewCombo; - private int extraComboOffset; protected override HitObject CreateHit(Vector2 position, bool newCombo, int comboOffset) { newCombo |= forceNewCombo; - comboOffset += extraComboOffset; - forceNewCombo = false; - extraComboOffset = 0; return new ConvertHit { X = position.X, NewCombo = newCombo, - ComboOffset = comboOffset }; } @@ -40,16 +35,12 @@ namespace osu.Game.Rulesets.Objects.Legacy.Catch List> nodeSamples) { newCombo |= forceNewCombo; - comboOffset += extraComboOffset; - forceNewCombo = false; - extraComboOffset = 0; return new ConvertSlider { X = position.X, NewCombo = FirstObject || newCombo, - ComboOffset = comboOffset, Path = new SliderPath(controlPoints, length), NodeSamples = nodeSamples, RepeatCount = repeatCount @@ -58,11 +49,6 @@ namespace osu.Game.Rulesets.Objects.Legacy.Catch protected override HitObject CreateSpinner(Vector2 position, bool newCombo, int comboOffset, double duration) { - // Convert spinners don't create the new combo themselves, but force the next non-spinner hitobject to create a new combo - // Their combo offset is still added to that next hitobject's combo index - forceNewCombo |= FormatVersion <= 8 || newCombo; - extraComboOffset += comboOffset; - return new ConvertSpinner { Duration = duration diff --git a/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertSlider.cs b/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertSlider.cs index 56790629b4..8b8bc2f4e1 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertSlider.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertSlider.cs @@ -13,7 +13,5 @@ namespace osu.Game.Rulesets.Objects.Legacy.Catch public float X { 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..3355bb3ee5 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertSpinner.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertSpinner.cs @@ -17,7 +17,5 @@ namespace osu.Game.Rulesets.Objects.Legacy.Catch 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/Osu/ConvertHit.cs b/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertHit.cs index 069366bad3..03b103b8fe 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertHit.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertHit.cs @@ -9,7 +9,7 @@ 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, IHasCombo, IHasLegacyBeatmapComboOffset { public Vector2 Position { get; set; } @@ -19,6 +19,8 @@ namespace osu.Game.Rulesets.Objects.Legacy.Osu public bool NewCombo { get; set; } - public int ComboOffset { get; set; } + public int LegacyBeatmapComboOffset { get; set; } + + int IHasLegacyBeatmapComboOffset.LegacyBeatmapComboIndex { get; set; } } } diff --git a/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertHitObjectParser.cs index 75ecab0b8f..451b714266 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertHitObjectParser.cs @@ -32,7 +32,7 @@ namespace osu.Game.Rulesets.Objects.Legacy.Osu { Position = position, NewCombo = FirstObject || newCombo, - ComboOffset = comboOffset + LegacyBeatmapComboOffset = comboOffset }; } @@ -49,7 +49,7 @@ namespace osu.Game.Rulesets.Objects.Legacy.Osu { Position = position, NewCombo = FirstObject || newCombo, - ComboOffset = comboOffset, + LegacyBeatmapComboOffset = comboOffset, Path = new SliderPath(controlPoints, length), NodeSamples = nodeSamples, RepeatCount = repeatCount diff --git a/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertSlider.cs b/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertSlider.cs index e947690668..c76c5a3f67 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 + internal sealed class ConvertSlider : Legacy.ConvertSlider, IHasPosition, IHasCombo, IHasLegacyBeatmapComboOffset { public Vector2 Position { get; set; } @@ -19,6 +19,8 @@ namespace osu.Game.Rulesets.Objects.Legacy.Osu public bool NewCombo { get; set; } - public int ComboOffset { get; set; } + public int LegacyBeatmapComboOffset { get; set; } + + int IHasLegacyBeatmapComboOffset.LegacyBeatmapComboIndex { get; set; } } } diff --git a/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertSpinner.cs b/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertSpinner.cs index e9e5ca8c94..328a380a96 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertSpinner.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertSpinner.cs @@ -22,7 +22,5 @@ namespace osu.Game.Rulesets.Objects.Legacy.Osu public float Y => Position.Y; public bool NewCombo { get; set; } - - public int ComboOffset { get; set; } } } diff --git a/osu.Game/Rulesets/Objects/Types/IHasCombo.cs b/osu.Game/Rulesets/Objects/Types/IHasCombo.cs index d1a4683a1d..7288684d27 100644 --- a/osu.Game/Rulesets/Objects/Types/IHasCombo.cs +++ b/osu.Game/Rulesets/Objects/Types/IHasCombo.cs @@ -12,10 +12,5 @@ namespace osu.Game.Rulesets.Objects.Types /// Whether the HitObject starts a new combo. /// bool NewCombo { get; } - - /// - /// When starting a new combo, the offset of the new combo relative to the current one. - /// - int ComboOffset { get; } } } diff --git a/osu.Game/Rulesets/Objects/Types/IHasLegacyBeatmapComboOffset.cs b/osu.Game/Rulesets/Objects/Types/IHasLegacyBeatmapComboOffset.cs new file mode 100644 index 0000000000..1a39192438 --- /dev/null +++ b/osu.Game/Rulesets/Objects/Types/IHasLegacyBeatmapComboOffset.cs @@ -0,0 +1,24 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +namespace osu.Game.Rulesets.Objects.Types +{ + /// + /// A type of that has a combo index with arbitrary offsets applied to use when retrieving legacy beatmap combo colours. + /// This is done in stable for hitobjects to skip combo colours from the beatmap skin (known as "colour hax"). + /// See https://osu.ppy.sh/wiki/en/osu%21_File_Formats/Osu_%28file_format%29#type for more information. + /// + public interface IHasLegacyBeatmapComboOffset + { + /// + /// The legacy offset of the new combo relative to the current one, when starting a new combo. + /// + int LegacyBeatmapComboOffset { get; } + + /// + /// The combo index with the applied, + /// to use for legacy beatmap skins to decide on the combo colour. + /// + int LegacyBeatmapComboIndex { get; set; } + } +} From 51ff59242d73cd6f7b83d1fa6059aeac068b168d Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 5 May 2021 07:36:30 +0300 Subject: [PATCH 02/19] Use legacy beatmap combo indices for legacy beatmap skins --- osu.Game/Skinning/LegacyBeatmapSkin.cs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/osu.Game/Skinning/LegacyBeatmapSkin.cs b/osu.Game/Skinning/LegacyBeatmapSkin.cs index 3ec205e897..7262169742 100644 --- a/osu.Game/Skinning/LegacyBeatmapSkin.cs +++ b/osu.Game/Skinning/LegacyBeatmapSkin.cs @@ -6,8 +6,11 @@ using osu.Framework.Bindables; using osu.Framework.IO.Stores; using osu.Game.Audio; using osu.Game.Beatmaps; +using osu.Game.Beatmaps.Formats; using osu.Game.IO; using osu.Game.Rulesets.Objects.Legacy; +using osu.Game.Rulesets.Objects.Types; +using osuTK.Graphics; namespace osu.Game.Skinning { @@ -39,6 +42,14 @@ namespace osu.Game.Skinning return base.GetConfig(lookup); } + protected override IBindable GetComboColour(IHasComboColours source, int comboIndex, IHasComboInformation combo) + { + if (combo is IHasLegacyBeatmapComboOffset legacyBeatmapCombo) + return base.GetComboColour(source, legacyBeatmapCombo.LegacyBeatmapComboIndex, combo); + + return base.GetComboColour(source, comboIndex, combo); + } + public override ISample GetSample(ISampleInfo sampleInfo) { if (sampleInfo is ConvertHitObjectParser.LegacyHitSampleInfo legacy && legacy.CustomSampleBank == 0) From fda6d8685caa8facab80bc66e129040055b432c1 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 5 May 2021 09:21:09 +0300 Subject: [PATCH 03/19] Let `LegacyBeatmapSkinColourTest` inherit from `TestPlayer` instead --- osu.Game/Tests/Beatmaps/LegacyBeatmapSkinColourTest.cs | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/osu.Game/Tests/Beatmaps/LegacyBeatmapSkinColourTest.cs b/osu.Game/Tests/Beatmaps/LegacyBeatmapSkinColourTest.cs index 051ede30b7..a38806aa71 100644 --- a/osu.Game/Tests/Beatmaps/LegacyBeatmapSkinColourTest.cs +++ b/osu.Game/Tests/Beatmaps/LegacyBeatmapSkinColourTest.cs @@ -10,7 +10,6 @@ using osu.Framework.Bindables; using osu.Framework.IO.Stores; using osu.Framework.Testing; using osu.Game.Beatmaps; -using osu.Game.Screens.Play; using osu.Game.Skinning; using osu.Game.Tests.Visual; using osuTK.Graphics; @@ -66,16 +65,12 @@ namespace osu.Game.Tests.Beatmaps protected virtual ExposedPlayer CreateTestPlayer(bool userHasCustomColours) => new ExposedPlayer(userHasCustomColours); - protected class ExposedPlayer : Player + protected class ExposedPlayer : TestPlayer { protected readonly bool UserHasCustomColours; public ExposedPlayer(bool userHasCustomColours) - : base(new PlayerConfiguration - { - AllowPause = false, - ShowResults = false, - }) + : base(false, false) { UserHasCustomColours = userHasCustomColours; } From 4cdfdcaddf00a3ce24ecdd40ae486cd8b39fce50 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 5 May 2021 09:21:38 +0300 Subject: [PATCH 04/19] Expand the combo colours used for testing To avoid potential false positive. --- osu.Game/Tests/Beatmaps/LegacyBeatmapSkinColourTest.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game/Tests/Beatmaps/LegacyBeatmapSkinColourTest.cs b/osu.Game/Tests/Beatmaps/LegacyBeatmapSkinColourTest.cs index a38806aa71..2315bbf709 100644 --- a/osu.Game/Tests/Beatmaps/LegacyBeatmapSkinColourTest.cs +++ b/osu.Game/Tests/Beatmaps/LegacyBeatmapSkinColourTest.cs @@ -107,6 +107,8 @@ namespace osu.Game.Tests.Beatmaps { new Color4(50, 100, 150, 255), new Color4(40, 80, 120, 255), + new Color4(25, 50, 75, 255), + new Color4(10, 20, 30, 255), }; public static readonly Color4 HYPER_DASH_COLOUR = Color4.DarkBlue; @@ -134,6 +136,8 @@ namespace osu.Game.Tests.Beatmaps { new Color4(150, 100, 50, 255), new Color4(20, 20, 20, 255), + new Color4(75, 50, 25, 255), + new Color4(80, 80, 80, 255), }; public static readonly Color4 HYPER_DASH_COLOUR = Color4.LightBlue; From 71d3b44c78cd3256aed8bd79b3b8593ef147f281 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 5 May 2021 09:22:19 +0300 Subject: [PATCH 05/19] Add test coverage for legacy beatmap combo offsets --- .../TestSceneLegacyBeatmapSkin.cs | 86 +++++++++++++++++-- 1 file changed, 81 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneLegacyBeatmapSkin.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneLegacyBeatmapSkin.cs index c26419b0e8..c24e54b4e8 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneLegacyBeatmapSkin.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneLegacyBeatmapSkin.cs @@ -1,6 +1,8 @@ // 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 System.Collections.Generic; using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; @@ -11,6 +13,7 @@ using osu.Game.Rulesets.Osu.Objects; using osu.Game.Skinning; using osu.Game.Tests.Beatmaps; using osuTK; +using osuTK.Graphics; namespace osu.Game.Rulesets.Osu.Tests { @@ -77,23 +80,96 @@ namespace osu.Game.Rulesets.Osu.Tests AddAssert("is custom user skin colours", () => TestPlayer.UsableComboColours.SequenceEqual(TestSkin.Colours)); } + [TestCase(true, true)] + [TestCase(true, false)] + [TestCase(false, true)] + [TestCase(false, false)] + public void TestLegacyOffsetWithBeatmapColours(bool userHasCustomColours, bool useBeatmapSkin) + { + TestBeatmap = new OsuCustomSkinWorkingBeatmap(audio, true, getHitCirclesWithLegacyOffsets()); + base.TestBeatmapComboColours(userHasCustomColours, useBeatmapSkin); + + assertCorrectObjectComboColours("is beatmap skin colours with legacy offsets applied", + TestBeatmapSkin.Colours, + (i, obj) => i + 1 + obj.LegacyBeatmapComboOffset); + } + + [TestCase(true)] + [TestCase(false)] + public void TestLegacyOffsetWithIgnoredBeatmapColours(bool useBeatmapSkin) + { + TestBeatmap = new OsuCustomSkinWorkingBeatmap(audio, true, getHitCirclesWithLegacyOffsets()); + base.TestBeatmapComboColoursOverride(useBeatmapSkin); + + assertCorrectObjectComboColours("is user skin colours without legacy offsets applied", + TestSkin.Colours, + (i, _) => i + 1); + } + + private void assertCorrectObjectComboColours(string description, Color4[] expectedColours, Func nextExpectedComboIndex) + { + AddUntilStep("wait for objects to become alive", () => + TestPlayer.DrawableRuleset.Playfield.AllHitObjects.Count() == TestPlayer.DrawableRuleset.Objects.Count()); + + AddAssert(description, () => + { + int index = 0; + + foreach (var drawable in TestPlayer.DrawableRuleset.Playfield.AllHitObjects) + { + index = nextExpectedComboIndex(index, (OsuHitObject)drawable.HitObject); + + if (drawable.AccentColour.Value != expectedColours[index % expectedColours.Length]) + return false; + } + + return true; + }); + } + + private static IEnumerable getHitCirclesWithLegacyOffsets() + { + var hitObjects = new List(); + + for (int i = 0; i < 5; i++) + { + hitObjects.Add(new HitCircle + { + StartTime = i, + Position = new Vector2(256, 192), + NewCombo = true, + LegacyBeatmapComboOffset = i, + }); + } + + return hitObjects; + } + private class OsuCustomSkinWorkingBeatmap : CustomSkinWorkingBeatmap { - public OsuCustomSkinWorkingBeatmap(AudioManager audio, bool hasColours) - : base(createBeatmap(), audio, hasColours) + public OsuCustomSkinWorkingBeatmap(AudioManager audio, bool hasColours, IEnumerable hitObjects = null) + : base(createBeatmap(hitObjects), audio, hasColours) { } - private static IBeatmap createBeatmap() => - new Beatmap + private static IBeatmap createBeatmap(IEnumerable hitObjects) + { + var beatmap = new Beatmap { BeatmapInfo = { BeatmapSet = new BeatmapSetInfo(), Ruleset = new OsuRuleset().RulesetInfo, }, - HitObjects = { new HitCircle { Position = new Vector2(256, 192) } } }; + + beatmap.HitObjects.AddRange(hitObjects ?? new[] + { + new HitCircle { Position = new Vector2(256, 192) } + }); + + return beatmap; + } } } } From bf6e98345c53fbaf0eb61e8d6542516567aa2b12 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 5 May 2021 14:18:51 +0300 Subject: [PATCH 06/19] Remove and update pre-existing test cases --- .../Formats/LegacyBeatmapDecoderTest.cs | 39 ++++--------------- 1 file changed, 7 insertions(+), 32 deletions(-) diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs index 0f82492e51..be88bb9b43 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs @@ -14,8 +14,6 @@ using osu.Game.Rulesets.Objects.Types; using osu.Game.Beatmaps.Formats; using osu.Game.Beatmaps.Timing; using osu.Game.IO; -using osu.Game.Rulesets.Catch; -using osu.Game.Rulesets.Catch.Beatmaps; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Legacy; @@ -310,7 +308,7 @@ namespace osu.Game.Tests.Beatmaps.Formats } [Test] - public void TestDecodeBeatmapComboOffsetsOsu() + public void TestDecodeLegacyBeatmapComboOffsets() { var decoder = new LegacyBeatmapDecoder(); @@ -323,35 +321,12 @@ namespace osu.Game.Tests.Beatmaps.Formats new OsuBeatmapProcessor(converted).PreProcess(); new OsuBeatmapProcessor(converted).PostProcess(); - Assert.AreEqual(4, ((IHasComboInformation)converted.HitObjects.ElementAt(0)).ComboIndex); - Assert.AreEqual(5, ((IHasComboInformation)converted.HitObjects.ElementAt(2)).ComboIndex); - Assert.AreEqual(5, ((IHasComboInformation)converted.HitObjects.ElementAt(4)).ComboIndex); - Assert.AreEqual(6, ((IHasComboInformation)converted.HitObjects.ElementAt(6)).ComboIndex); - Assert.AreEqual(11, ((IHasComboInformation)converted.HitObjects.ElementAt(8)).ComboIndex); - Assert.AreEqual(14, ((IHasComboInformation)converted.HitObjects.ElementAt(11)).ComboIndex); - } - } - - [Test] - public void TestDecodeBeatmapComboOffsetsCatch() - { - var decoder = new LegacyBeatmapDecoder(); - - using (var resStream = TestResources.OpenResource("hitobject-combo-offset.osu")) - using (var stream = new LineBufferedReader(resStream)) - { - var beatmap = decoder.Decode(stream); - - var converted = new CatchBeatmapConverter(beatmap, new CatchRuleset()).Convert(); - new CatchBeatmapProcessor(converted).PreProcess(); - new CatchBeatmapProcessor(converted).PostProcess(); - - Assert.AreEqual(4, ((IHasComboInformation)converted.HitObjects.ElementAt(0)).ComboIndex); - Assert.AreEqual(5, ((IHasComboInformation)converted.HitObjects.ElementAt(2)).ComboIndex); - Assert.AreEqual(5, ((IHasComboInformation)converted.HitObjects.ElementAt(4)).ComboIndex); - Assert.AreEqual(6, ((IHasComboInformation)converted.HitObjects.ElementAt(6)).ComboIndex); - Assert.AreEqual(11, ((IHasComboInformation)converted.HitObjects.ElementAt(8)).ComboIndex); - Assert.AreEqual(14, ((IHasComboInformation)converted.HitObjects.ElementAt(11)).ComboIndex); + Assert.AreEqual(4, ((IHasLegacyBeatmapComboOffset)converted.HitObjects.ElementAt(0)).LegacyBeatmapComboIndex); + Assert.AreEqual(5, ((IHasLegacyBeatmapComboOffset)converted.HitObjects.ElementAt(2)).LegacyBeatmapComboIndex); + Assert.AreEqual(5, ((IHasLegacyBeatmapComboOffset)converted.HitObjects.ElementAt(4)).LegacyBeatmapComboIndex); + Assert.AreEqual(6, ((IHasLegacyBeatmapComboOffset)converted.HitObjects.ElementAt(6)).LegacyBeatmapComboIndex); + Assert.AreEqual(11, ((IHasLegacyBeatmapComboOffset)converted.HitObjects.ElementAt(8)).LegacyBeatmapComboIndex); + Assert.AreEqual(14, ((IHasLegacyBeatmapComboOffset)converted.HitObjects.ElementAt(11)).LegacyBeatmapComboIndex); } } From 6fb9eb8b33227284c889760d6ba45a22408f04e4 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 5 May 2021 14:24:13 +0300 Subject: [PATCH 07/19] Move legacy beatmap combo offset to osu! processor Better suited there, I intiailly wanted the whole legacy interface to reside in `osu.Game.Rulesets.Osu` but it's required in `ConvertHitObjectParser` and that's in the main game project, so had to leave the interface as-is for now. --- .../Beatmaps/OsuBeatmapProcessor.cs | 19 +++++++++++++++++++ osu.Game/Beatmaps/BeatmapProcessor.cs | 10 ---------- 2 files changed, 19 insertions(+), 10 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapProcessor.cs b/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapProcessor.cs index f51f04bf87..5bff3c7bcc 100644 --- a/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapProcessor.cs +++ b/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapProcessor.cs @@ -2,9 +2,11 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Linq; using osu.Framework.Graphics; using osu.Game.Beatmaps; using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Osu.Objects; using osuTK; @@ -19,6 +21,23 @@ namespace osu.Game.Rulesets.Osu.Beatmaps { } + public override void PreProcess() + { + base.PreProcess(); + + OsuHitObject lastObj = null; + + foreach (var obj in Beatmap.HitObjects.OfType()) + { + if (obj.NewCombo) + obj.LegacyBeatmapComboIndex = (lastObj?.LegacyBeatmapComboIndex ?? 0) + obj.LegacyBeatmapComboOffset + 1; + else if (lastObj != null) + obj.LegacyBeatmapComboIndex = lastObj.LegacyBeatmapComboIndex; + + lastObj = obj; + } + } + public override void PostProcess() { base.PostProcess(); diff --git a/osu.Game/Beatmaps/BeatmapProcessor.cs b/osu.Game/Beatmaps/BeatmapProcessor.cs index ac2d7d0fc1..f75f04b26f 100644 --- a/osu.Game/Beatmaps/BeatmapProcessor.cs +++ b/osu.Game/Beatmaps/BeatmapProcessor.cs @@ -48,16 +48,6 @@ namespace osu.Game.Beatmaps obj.ComboIndex = lastObj.ComboIndex; } - if (obj is IHasLegacyBeatmapComboOffset legacyObj) - { - var lastLegacyObj = (IHasLegacyBeatmapComboOffset)lastObj; - - if (obj.NewCombo) - legacyObj.LegacyBeatmapComboIndex = (lastLegacyObj?.LegacyBeatmapComboIndex ?? 0) + legacyObj.LegacyBeatmapComboOffset + 1; - else if (lastLegacyObj != null) - legacyObj.LegacyBeatmapComboIndex = lastLegacyObj.LegacyBeatmapComboIndex; - } - lastObj = obj; } } From cc24d8a6bd9627645de6afec29d514ad9e11e884 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 5 May 2021 14:27:37 +0300 Subject: [PATCH 08/19] Remove unused using directive I literally noticed it after I pushed, god damn it. --- osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapProcessor.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapProcessor.cs b/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapProcessor.cs index 5bff3c7bcc..b7ec8795ca 100644 --- a/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapProcessor.cs +++ b/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapProcessor.cs @@ -6,7 +6,6 @@ using System.Linq; using osu.Framework.Graphics; using osu.Game.Beatmaps; using osu.Game.Rulesets.Objects; -using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Osu.Objects; using osuTK; From a1001542f34f20727df86b244184afd68141ce49 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 20 Jul 2021 10:28:24 +0300 Subject: [PATCH 09/19] Update outdated test cases --- .../TestSceneLegacyBeatmapSkin.cs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneLegacyBeatmapSkin.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneLegacyBeatmapSkin.cs index 13df5070d0..a009c560cb 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneLegacyBeatmapSkin.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneLegacyBeatmapSkin.cs @@ -86,9 +86,8 @@ namespace osu.Game.Rulesets.Osu.Tests [TestCase(false, false)] public void TestLegacyOffsetWithBeatmapColours(bool userHasCustomColours, bool useBeatmapSkin) { - TestBeatmap = new OsuCustomSkinWorkingBeatmap(audio, true, getHitCirclesWithLegacyOffsets()); - base.TestBeatmapComboColours(userHasCustomColours, useBeatmapSkin); - + PrepareBeatmap(() => new OsuCustomSkinWorkingBeatmap(audio, true, getHitCirclesWithLegacyOffsets())); + ConfigureTest(useBeatmapSkin, true, userHasCustomColours); assertCorrectObjectComboColours("is beatmap skin colours with legacy offsets applied", TestBeatmapSkin.Colours, (i, obj) => i + 1 + obj.LegacyBeatmapComboOffset); @@ -98,9 +97,8 @@ namespace osu.Game.Rulesets.Osu.Tests [TestCase(false)] public void TestLegacyOffsetWithIgnoredBeatmapColours(bool useBeatmapSkin) { - TestBeatmap = new OsuCustomSkinWorkingBeatmap(audio, true, getHitCirclesWithLegacyOffsets()); - base.TestBeatmapComboColoursOverride(useBeatmapSkin); - + PrepareBeatmap(() => new OsuCustomSkinWorkingBeatmap(audio, true, getHitCirclesWithLegacyOffsets())); + ConfigureTest(useBeatmapSkin, false, true); assertCorrectObjectComboColours("is user skin colours without legacy offsets applied", TestSkin.Colours, (i, _) => i + 1); From 9d92b795fac46560120cb35cfb9222459c8e8b1d Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 20 Jul 2021 14:15:43 +0300 Subject: [PATCH 10/19] Revert making `ComboOffset`s legacy and define `BeatmapSkinComboIndex` instead --- .../Beatmaps/CatchBeatmapConverter.cs | 3 ++ .../Objects/CatchHitObject.cs | 4 ++ .../TestSceneLegacyBeatmapSkin.cs | 4 +- .../Beatmaps/OsuBeatmapConverter.cs | 6 +-- .../Beatmaps/OsuBeatmapProcessor.cs | 18 --------- osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs | 16 +++++--- .../Formats/LegacyBeatmapDecoderTest.cs | 39 +++++++++++++++---- .../TestSceneHitObjectAccentColour.cs | 3 ++ osu.Game/Beatmaps/BeatmapProcessor.cs | 2 + .../Beatmaps/Formats/LegacyBeatmapEncoder.cs | 5 +-- .../Objects/Legacy/Catch/ConvertHit.cs | 2 + .../Legacy/Catch/ConvertHitObjectParser.cs | 14 +++++++ .../Objects/Legacy/Catch/ConvertSlider.cs | 2 + .../Objects/Legacy/Catch/ConvertSpinner.cs | 2 + .../Rulesets/Objects/Legacy/Osu/ConvertHit.cs | 6 +-- .../Legacy/Osu/ConvertHitObjectParser.cs | 4 +- .../Objects/Legacy/Osu/ConvertSlider.cs | 6 +-- .../Objects/Legacy/Osu/ConvertSpinner.cs | 2 + osu.Game/Rulesets/Objects/Types/IHasCombo.cs | 5 +++ .../Objects/Types/IHasComboInformation.cs | 6 +++ .../Types/IHasLegacyBeatmapComboOffset.cs | 24 ------------ osu.Game/Skinning/LegacyBeatmapSkin.cs | 7 +--- 22 files changed, 102 insertions(+), 78 deletions(-) delete mode 100644 osu.Game/Rulesets/Objects/Types/IHasLegacyBeatmapComboOffset.cs diff --git a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs index fca2291f41..7774a7da09 100644 --- a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs +++ b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs @@ -39,6 +39,7 @@ namespace osu.Game.Rulesets.Catch.Beatmaps RepeatCount = curveData.RepeatCount, X = xPositionData?.X ?? 0, NewCombo = comboData?.NewCombo ?? false, + ComboOffset = comboData?.ComboOffset ?? 0, LegacyLastTickOffset = (obj as IHasLegacyLastTickOffset)?.LegacyLastTickOffset ?? 0, LegacyConvertedY = yPositionData?.Y ?? CatchHitObject.DEFAULT_LEGACY_CONVERT_Y }.Yield(); @@ -50,6 +51,7 @@ namespace osu.Game.Rulesets.Catch.Beatmaps Samples = obj.Samples, Duration = endTime.Duration, NewCombo = comboData?.NewCombo ?? false, + ComboOffset = comboData?.ComboOffset ?? 0, }.Yield(); default: @@ -58,6 +60,7 @@ namespace osu.Game.Rulesets.Catch.Beatmaps StartTime = obj.StartTime, Samples = obj.Samples, NewCombo = comboData?.NewCombo ?? false, + ComboOffset = comboData?.ComboOffset ?? 0, X = xPositionData?.X ?? 0, LegacyConvertedY = yPositionData?.Y ?? CatchHitObject.DEFAULT_LEGACY_CONVERT_Y }.Yield(); diff --git a/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs b/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs index f0515ee314..bad558fcad 100644 --- a/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs +++ b/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs @@ -77,6 +77,8 @@ namespace osu.Game.Rulesets.Catch.Objects public virtual bool NewCombo { get; set; } + public int ComboOffset { get; set; } + public Bindable IndexInCurrentComboBindable { get; } = new Bindable(); public int IndexInCurrentCombo @@ -93,6 +95,8 @@ namespace osu.Game.Rulesets.Catch.Objects set => ComboIndexBindable.Value = value; } + public int BeatmapSkinComboIndex { get; set; } + public Bindable LastInComboBindable { get; } = new Bindable(); /// diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneLegacyBeatmapSkin.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneLegacyBeatmapSkin.cs index a009c560cb..f514753890 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneLegacyBeatmapSkin.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneLegacyBeatmapSkin.cs @@ -90,7 +90,7 @@ namespace osu.Game.Rulesets.Osu.Tests ConfigureTest(useBeatmapSkin, true, userHasCustomColours); assertCorrectObjectComboColours("is beatmap skin colours with legacy offsets applied", TestBeatmapSkin.Colours, - (i, obj) => i + 1 + obj.LegacyBeatmapComboOffset); + (i, obj) => i + 1 + obj.ComboOffset); } [TestCase(true)] @@ -136,7 +136,7 @@ namespace osu.Game.Rulesets.Osu.Tests StartTime = i, Position = new Vector2(256, 192), NewCombo = true, - LegacyBeatmapComboOffset = i, + ComboOffset = i, }); } diff --git a/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapConverter.cs b/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapConverter.cs index d812f86938..a2fc4848af 100644 --- a/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapConverter.cs +++ b/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapConverter.cs @@ -40,7 +40,7 @@ namespace osu.Game.Rulesets.Osu.Beatmaps RepeatCount = curveData.RepeatCount, Position = positionData?.Position ?? Vector2.Zero, NewCombo = comboData?.NewCombo ?? false, - LegacyBeatmapComboOffset = (original as IHasLegacyBeatmapComboOffset)?.LegacyBeatmapComboOffset ?? 0, + ComboOffset = comboData?.ComboOffset ?? 0, LegacyLastTickOffset = (original as IHasLegacyLastTickOffset)?.LegacyLastTickOffset, // prior to v8, speed multipliers don't adjust for how many ticks are generated over the same distance. // this results in more (or less) ticks being generated in ()) - { - if (obj.NewCombo) - obj.LegacyBeatmapComboIndex = (lastObj?.LegacyBeatmapComboIndex ?? 0) + obj.LegacyBeatmapComboOffset + 1; - else if (lastObj != null) - obj.LegacyBeatmapComboIndex = lastObj.LegacyBeatmapComboIndex; - - lastObj = obj; - } - } - public override void PostProcess() { base.PostProcess(); diff --git a/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs b/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs index 6de061978c..db8a02aa0f 100644 --- a/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs +++ b/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs @@ -14,7 +14,7 @@ using osu.Game.Rulesets.Scoring; namespace osu.Game.Rulesets.Osu.Objects { - public abstract class OsuHitObject : HitObject, IHasComboInformation, IHasPosition, IHasLegacyBeatmapComboOffset + public abstract class OsuHitObject : HitObject, IHasComboInformation, IHasPosition { /// /// The radius of hit objects (ie. the radius of a ). @@ -73,6 +73,14 @@ namespace osu.Game.Rulesets.Osu.Objects public virtual bool NewCombo { get; set; } + public readonly Bindable ComboOffsetBindable = new Bindable(); + + public int ComboOffset + { + get => ComboOffsetBindable.Value; + set => ComboOffsetBindable.Value = value; + } + public Bindable IndexInCurrentComboBindable { get; } = new Bindable(); public virtual int IndexInCurrentCombo @@ -89,6 +97,8 @@ namespace osu.Game.Rulesets.Osu.Objects set => ComboIndexBindable.Value = value; } + public int BeatmapSkinComboIndex { get; set; } + public Bindable LastInComboBindable { get; } = new Bindable(); public bool LastInCombo @@ -97,10 +107,6 @@ namespace osu.Game.Rulesets.Osu.Objects set => LastInComboBindable.Value = value; } - public int LegacyBeatmapComboOffset { get; set; } - - public int LegacyBeatmapComboIndex { get; set; } - protected OsuHitObject() { StackHeightBindable.BindValueChanged(height => diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs index be88bb9b43..42f89a758e 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs @@ -14,6 +14,8 @@ using osu.Game.Rulesets.Objects.Types; using osu.Game.Beatmaps.Formats; using osu.Game.Beatmaps.Timing; using osu.Game.IO; +using osu.Game.Rulesets.Catch; +using osu.Game.Rulesets.Catch.Beatmaps; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Legacy; @@ -308,7 +310,7 @@ namespace osu.Game.Tests.Beatmaps.Formats } [Test] - public void TestDecodeLegacyBeatmapComboOffsets() + public void TestDecodeBeatmapComboOffsetsOsu() { var decoder = new LegacyBeatmapDecoder(); @@ -321,12 +323,35 @@ namespace osu.Game.Tests.Beatmaps.Formats new OsuBeatmapProcessor(converted).PreProcess(); new OsuBeatmapProcessor(converted).PostProcess(); - Assert.AreEqual(4, ((IHasLegacyBeatmapComboOffset)converted.HitObjects.ElementAt(0)).LegacyBeatmapComboIndex); - Assert.AreEqual(5, ((IHasLegacyBeatmapComboOffset)converted.HitObjects.ElementAt(2)).LegacyBeatmapComboIndex); - Assert.AreEqual(5, ((IHasLegacyBeatmapComboOffset)converted.HitObjects.ElementAt(4)).LegacyBeatmapComboIndex); - Assert.AreEqual(6, ((IHasLegacyBeatmapComboOffset)converted.HitObjects.ElementAt(6)).LegacyBeatmapComboIndex); - Assert.AreEqual(11, ((IHasLegacyBeatmapComboOffset)converted.HitObjects.ElementAt(8)).LegacyBeatmapComboIndex); - Assert.AreEqual(14, ((IHasLegacyBeatmapComboOffset)converted.HitObjects.ElementAt(11)).LegacyBeatmapComboIndex); + Assert.AreEqual(4, ((IHasComboInformation)converted.HitObjects.ElementAt(0)).BeatmapSkinComboIndex); + Assert.AreEqual(5, ((IHasComboInformation)converted.HitObjects.ElementAt(2)).BeatmapSkinComboIndex); + Assert.AreEqual(5, ((IHasComboInformation)converted.HitObjects.ElementAt(4)).BeatmapSkinComboIndex); + Assert.AreEqual(6, ((IHasComboInformation)converted.HitObjects.ElementAt(6)).BeatmapSkinComboIndex); + Assert.AreEqual(11, ((IHasComboInformation)converted.HitObjects.ElementAt(8)).BeatmapSkinComboIndex); + Assert.AreEqual(14, ((IHasComboInformation)converted.HitObjects.ElementAt(11)).BeatmapSkinComboIndex); + } + } + + [Test] + public void TestDecodeBeatmapComboOffsetsCatch() + { + var decoder = new LegacyBeatmapDecoder(); + + using (var resStream = TestResources.OpenResource("hitobject-combo-offset.osu")) + using (var stream = new LineBufferedReader(resStream)) + { + var beatmap = decoder.Decode(stream); + + var converted = new CatchBeatmapConverter(beatmap, new CatchRuleset()).Convert(); + new CatchBeatmapProcessor(converted).PreProcess(); + new CatchBeatmapProcessor(converted).PostProcess(); + + Assert.AreEqual(4, ((IHasComboInformation)converted.HitObjects.ElementAt(0)).BeatmapSkinComboIndex); + Assert.AreEqual(5, ((IHasComboInformation)converted.HitObjects.ElementAt(2)).BeatmapSkinComboIndex); + Assert.AreEqual(5, ((IHasComboInformation)converted.HitObjects.ElementAt(4)).BeatmapSkinComboIndex); + Assert.AreEqual(6, ((IHasComboInformation)converted.HitObjects.ElementAt(6)).BeatmapSkinComboIndex); + Assert.AreEqual(11, ((IHasComboInformation)converted.HitObjects.ElementAt(8)).BeatmapSkinComboIndex); + Assert.AreEqual(14, ((IHasComboInformation)converted.HitObjects.ElementAt(11)).BeatmapSkinComboIndex); } } diff --git a/osu.Game.Tests/Gameplay/TestSceneHitObjectAccentColour.cs b/osu.Game.Tests/Gameplay/TestSceneHitObjectAccentColour.cs index 3dd13da6c1..f6f482e254 100644 --- a/osu.Game.Tests/Gameplay/TestSceneHitObjectAccentColour.cs +++ b/osu.Game.Tests/Gameplay/TestSceneHitObjectAccentColour.cs @@ -82,6 +82,7 @@ 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(); @@ -99,6 +100,8 @@ namespace osu.Game.Tests.Gameplay set => ComboIndexBindable.Value = value; } + public int BeatmapSkinComboIndex { get; set; } + public Bindable LastInComboBindable { get; } = new Bindable(); public bool LastInCombo diff --git a/osu.Game/Beatmaps/BeatmapProcessor.cs b/osu.Game/Beatmaps/BeatmapProcessor.cs index f75f04b26f..9121222bd7 100644 --- a/osu.Game/Beatmaps/BeatmapProcessor.cs +++ b/osu.Game/Beatmaps/BeatmapProcessor.cs @@ -38,6 +38,7 @@ namespace osu.Game.Beatmaps { obj.IndexInCurrentCombo = 0; obj.ComboIndex = (lastObj?.ComboIndex ?? 0) + 1; + obj.BeatmapSkinComboIndex = (lastObj?.BeatmapSkinComboIndex ?? 0) + obj.ComboOffset + 1; if (lastObj != null) lastObj.LastInCombo = true; @@ -46,6 +47,7 @@ namespace osu.Game.Beatmaps { obj.IndexInCurrentCombo = lastObj.IndexInCurrentCombo + 1; obj.ComboIndex = lastObj.ComboIndex; + obj.BeatmapSkinComboIndex = lastObj.BeatmapSkinComboIndex; } lastObj = obj; diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs index fed8af5b95..f14f6ec10c 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs @@ -289,11 +289,10 @@ namespace osu.Game.Beatmaps.Formats if (hitObject is IHasCombo combo) { + type = (LegacyHitObjectType)(combo.ComboOffset << 4); + if (combo.NewCombo) type |= LegacyHitObjectType.NewCombo; - - if (hitObject is IHasLegacyBeatmapComboOffset comboOffset) - type |= (LegacyHitObjectType)(comboOffset.LegacyBeatmapComboOffset << 4); } switch (hitObject) diff --git a/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertHit.cs b/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertHit.cs index 0b0a18ba12..12b4812824 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertHit.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertHit.cs @@ -18,5 +18,7 @@ namespace osu.Game.Rulesets.Objects.Legacy.Catch public Vector2 Position { get; set; } public bool NewCombo { get; set; } + + public int ComboOffset { get; set; } } } diff --git a/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertHitObjectParser.cs index d304ea8d39..c29179f749 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertHitObjectParser.cs @@ -18,16 +18,21 @@ namespace osu.Game.Rulesets.Objects.Legacy.Catch } private bool forceNewCombo; + private int extraComboOffset; protected override HitObject CreateHit(Vector2 position, bool newCombo, int comboOffset) { newCombo |= forceNewCombo; + comboOffset += extraComboOffset; + forceNewCombo = false; + extraComboOffset = 0; return new ConvertHit { Position = position, NewCombo = newCombo, + ComboOffset = comboOffset }; } @@ -35,12 +40,16 @@ namespace osu.Game.Rulesets.Objects.Legacy.Catch List> nodeSamples) { newCombo |= forceNewCombo; + comboOffset += extraComboOffset; + forceNewCombo = false; + extraComboOffset = 0; return new ConvertSlider { Position = position, NewCombo = FirstObject || newCombo, + ComboOffset = comboOffset, Path = new SliderPath(controlPoints, length), NodeSamples = nodeSamples, RepeatCount = repeatCount @@ -49,6 +58,11 @@ namespace osu.Game.Rulesets.Objects.Legacy.Catch protected override HitObject CreateSpinner(Vector2 position, bool newCombo, int comboOffset, double duration) { + // Convert spinners don't create the new combo themselves, but force the next non-spinner hitobject to create a new combo + // Their combo offset is still added to that next hitobject's combo index + forceNewCombo |= FormatVersion <= 8 || newCombo; + extraComboOffset += comboOffset; + return new ConvertSpinner { Duration = duration diff --git a/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertSlider.cs b/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertSlider.cs index 6edf15478f..fb1afed3b4 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertSlider.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertSlider.cs @@ -18,5 +18,7 @@ namespace osu.Game.Rulesets.Objects.Legacy.Catch 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 3355bb3ee5..014494ec54 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertSpinner.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertSpinner.cs @@ -17,5 +17,7 @@ namespace osu.Game.Rulesets.Objects.Legacy.Catch 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/Osu/ConvertHit.cs b/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertHit.cs index 03b103b8fe..069366bad3 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertHit.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertHit.cs @@ -9,7 +9,7 @@ namespace osu.Game.Rulesets.Objects.Legacy.Osu /// /// Legacy osu! Hit-type, used for parsing Beatmaps. /// - internal sealed class ConvertHit : ConvertHitObject, IHasPosition, IHasCombo, IHasLegacyBeatmapComboOffset + internal sealed class ConvertHit : ConvertHitObject, IHasPosition, IHasCombo { public Vector2 Position { get; set; } @@ -19,8 +19,6 @@ namespace osu.Game.Rulesets.Objects.Legacy.Osu public bool NewCombo { get; set; } - public int LegacyBeatmapComboOffset { get; set; } - - int IHasLegacyBeatmapComboOffset.LegacyBeatmapComboIndex { get; set; } + public int ComboOffset { get; set; } } } diff --git a/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertHitObjectParser.cs index 451b714266..75ecab0b8f 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertHitObjectParser.cs @@ -32,7 +32,7 @@ namespace osu.Game.Rulesets.Objects.Legacy.Osu { Position = position, NewCombo = FirstObject || newCombo, - LegacyBeatmapComboOffset = comboOffset + ComboOffset = comboOffset }; } @@ -49,7 +49,7 @@ namespace osu.Game.Rulesets.Objects.Legacy.Osu { Position = position, NewCombo = FirstObject || newCombo, - LegacyBeatmapComboOffset = comboOffset, + ComboOffset = comboOffset, Path = new SliderPath(controlPoints, length), NodeSamples = nodeSamples, RepeatCount = repeatCount diff --git a/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertSlider.cs b/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertSlider.cs index c76c5a3f67..e947690668 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, IHasLegacyBeatmapComboOffset + internal sealed class ConvertSlider : Legacy.ConvertSlider, IHasPosition, IHasCombo { public Vector2 Position { get; set; } @@ -19,8 +19,6 @@ namespace osu.Game.Rulesets.Objects.Legacy.Osu public bool NewCombo { get; set; } - public int LegacyBeatmapComboOffset { get; set; } - - int IHasLegacyBeatmapComboOffset.LegacyBeatmapComboIndex { get; set; } + public int ComboOffset { get; set; } } } diff --git a/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertSpinner.cs b/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertSpinner.cs index 328a380a96..e9e5ca8c94 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertSpinner.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertSpinner.cs @@ -22,5 +22,7 @@ namespace osu.Game.Rulesets.Objects.Legacy.Osu public float Y => Position.Y; public bool NewCombo { get; set; } + + public int ComboOffset { get; set; } } } diff --git a/osu.Game/Rulesets/Objects/Types/IHasCombo.cs b/osu.Game/Rulesets/Objects/Types/IHasCombo.cs index 7288684d27..d1a4683a1d 100644 --- a/osu.Game/Rulesets/Objects/Types/IHasCombo.cs +++ b/osu.Game/Rulesets/Objects/Types/IHasCombo.cs @@ -12,5 +12,10 @@ namespace osu.Game.Rulesets.Objects.Types /// Whether the HitObject starts a new combo. /// bool NewCombo { get; } + + /// + /// When starting a new combo, the offset of the new combo relative to the current one. + /// + int ComboOffset { get; } } } diff --git a/osu.Game/Rulesets/Objects/Types/IHasComboInformation.cs b/osu.Game/Rulesets/Objects/Types/IHasComboInformation.cs index 03e6f76cca..46d7bef498 100644 --- a/osu.Game/Rulesets/Objects/Types/IHasComboInformation.cs +++ b/osu.Game/Rulesets/Objects/Types/IHasComboInformation.cs @@ -26,6 +26,12 @@ namespace osu.Game.Rulesets.Objects.Types /// int ComboIndex { get; set; } + /// + /// A with the of this and all previous hitobjects applied to it. + /// This is used primarily for beatmap skins during combo colour retrieval, rather than the regular . + /// + int BeatmapSkinComboIndex { get; set; } + /// /// Whether the HitObject starts a new combo. /// diff --git a/osu.Game/Rulesets/Objects/Types/IHasLegacyBeatmapComboOffset.cs b/osu.Game/Rulesets/Objects/Types/IHasLegacyBeatmapComboOffset.cs deleted file mode 100644 index 1a39192438..0000000000 --- a/osu.Game/Rulesets/Objects/Types/IHasLegacyBeatmapComboOffset.cs +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -namespace osu.Game.Rulesets.Objects.Types -{ - /// - /// A type of that has a combo index with arbitrary offsets applied to use when retrieving legacy beatmap combo colours. - /// This is done in stable for hitobjects to skip combo colours from the beatmap skin (known as "colour hax"). - /// See https://osu.ppy.sh/wiki/en/osu%21_File_Formats/Osu_%28file_format%29#type for more information. - /// - public interface IHasLegacyBeatmapComboOffset - { - /// - /// The legacy offset of the new combo relative to the current one, when starting a new combo. - /// - int LegacyBeatmapComboOffset { get; } - - /// - /// The combo index with the applied, - /// to use for legacy beatmap skins to decide on the combo colour. - /// - int LegacyBeatmapComboIndex { get; set; } - } -} diff --git a/osu.Game/Skinning/LegacyBeatmapSkin.cs b/osu.Game/Skinning/LegacyBeatmapSkin.cs index 36c9b635c2..5b46a21381 100644 --- a/osu.Game/Skinning/LegacyBeatmapSkin.cs +++ b/osu.Game/Skinning/LegacyBeatmapSkin.cs @@ -63,12 +63,7 @@ namespace osu.Game.Skinning } protected override IBindable GetComboColour(IHasComboColours source, int comboIndex, IHasComboInformation combo) - { - if (combo is IHasLegacyBeatmapComboOffset legacyBeatmapCombo) - return base.GetComboColour(source, legacyBeatmapCombo.LegacyBeatmapComboIndex, combo); - - return base.GetComboColour(source, comboIndex, combo); - } + => base.GetComboColour(source, combo.BeatmapSkinComboIndex, combo); public override ISample GetSample(ISampleInfo sampleInfo) { From 300b3f22b1e943aad5d62da0cc3a7db34b116e53 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 20 Jul 2021 14:35:21 +0300 Subject: [PATCH 11/19] Remove no longer correct usage of "legacy" in offsets --- osu.Game.Rulesets.Osu.Tests/TestSceneLegacyBeatmapSkin.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneLegacyBeatmapSkin.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneLegacyBeatmapSkin.cs index f514753890..f2f68e805c 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneLegacyBeatmapSkin.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneLegacyBeatmapSkin.cs @@ -84,22 +84,22 @@ namespace osu.Game.Rulesets.Osu.Tests [TestCase(true, false)] [TestCase(false, true)] [TestCase(false, false)] - public void TestLegacyOffsetWithBeatmapColours(bool userHasCustomColours, bool useBeatmapSkin) + public void TestComboOffsetWithBeatmapColours(bool userHasCustomColours, bool useBeatmapSkin) { PrepareBeatmap(() => new OsuCustomSkinWorkingBeatmap(audio, true, getHitCirclesWithLegacyOffsets())); ConfigureTest(useBeatmapSkin, true, userHasCustomColours); - assertCorrectObjectComboColours("is beatmap skin colours with legacy offsets applied", + assertCorrectObjectComboColours("is beatmap skin colours with combo offsets applied", TestBeatmapSkin.Colours, (i, obj) => i + 1 + obj.ComboOffset); } [TestCase(true)] [TestCase(false)] - public void TestLegacyOffsetWithIgnoredBeatmapColours(bool useBeatmapSkin) + public void TestComboOffsetWithIgnoredBeatmapColours(bool useBeatmapSkin) { PrepareBeatmap(() => new OsuCustomSkinWorkingBeatmap(audio, true, getHitCirclesWithLegacyOffsets())); ConfigureTest(useBeatmapSkin, false, true); - assertCorrectObjectComboColours("is user skin colours without legacy offsets applied", + assertCorrectObjectComboColours("is user skin colours without combo offsets applied", TestSkin.Colours, (i, _) => i + 1); } From 399c3b0be88d2c30ce88b5fb0f17c7eb6c69644b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 21 Jul 2021 17:32:56 +0900 Subject: [PATCH 12/19] Rename property, reword xmldoc and improve readability of update code --- .../Objects/CatchHitObject.cs | 2 +- osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs | 2 +- .../Formats/LegacyBeatmapDecoderTest.cs | 24 +++++++++---------- .../TestSceneHitObjectAccentColour.cs | 2 +- osu.Game/Beatmaps/BeatmapProcessor.cs | 14 +++++------ .../Objects/Types/IHasComboInformation.cs | 6 ++--- osu.Game/Skinning/LegacyBeatmapSkin.cs | 2 +- 7 files changed, 25 insertions(+), 27 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs b/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs index bad558fcad..33353ec423 100644 --- a/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs +++ b/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs @@ -95,7 +95,7 @@ namespace osu.Game.Rulesets.Catch.Objects set => ComboIndexBindable.Value = value; } - public int BeatmapSkinComboIndex { get; set; } + public int ComboIndexWithOffsets { get; set; } public Bindable LastInComboBindable { get; } = new Bindable(); diff --git a/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs b/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs index db8a02aa0f..7c60480b00 100644 --- a/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs +++ b/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs @@ -97,7 +97,7 @@ namespace osu.Game.Rulesets.Osu.Objects set => ComboIndexBindable.Value = value; } - public int BeatmapSkinComboIndex { get; set; } + public int ComboIndexWithOffsets { get; set; } public Bindable LastInComboBindable { get; } = new Bindable(); diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs index 42f89a758e..4fe1cf3790 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs @@ -323,12 +323,12 @@ namespace osu.Game.Tests.Beatmaps.Formats new OsuBeatmapProcessor(converted).PreProcess(); new OsuBeatmapProcessor(converted).PostProcess(); - Assert.AreEqual(4, ((IHasComboInformation)converted.HitObjects.ElementAt(0)).BeatmapSkinComboIndex); - Assert.AreEqual(5, ((IHasComboInformation)converted.HitObjects.ElementAt(2)).BeatmapSkinComboIndex); - Assert.AreEqual(5, ((IHasComboInformation)converted.HitObjects.ElementAt(4)).BeatmapSkinComboIndex); - Assert.AreEqual(6, ((IHasComboInformation)converted.HitObjects.ElementAt(6)).BeatmapSkinComboIndex); - Assert.AreEqual(11, ((IHasComboInformation)converted.HitObjects.ElementAt(8)).BeatmapSkinComboIndex); - Assert.AreEqual(14, ((IHasComboInformation)converted.HitObjects.ElementAt(11)).BeatmapSkinComboIndex); + Assert.AreEqual(4, ((IHasComboInformation)converted.HitObjects.ElementAt(0)).ComboIndexWithOffsets); + Assert.AreEqual(5, ((IHasComboInformation)converted.HitObjects.ElementAt(2)).ComboIndexWithOffsets); + Assert.AreEqual(5, ((IHasComboInformation)converted.HitObjects.ElementAt(4)).ComboIndexWithOffsets); + Assert.AreEqual(6, ((IHasComboInformation)converted.HitObjects.ElementAt(6)).ComboIndexWithOffsets); + Assert.AreEqual(11, ((IHasComboInformation)converted.HitObjects.ElementAt(8)).ComboIndexWithOffsets); + Assert.AreEqual(14, ((IHasComboInformation)converted.HitObjects.ElementAt(11)).ComboIndexWithOffsets); } } @@ -346,12 +346,12 @@ namespace osu.Game.Tests.Beatmaps.Formats new CatchBeatmapProcessor(converted).PreProcess(); new CatchBeatmapProcessor(converted).PostProcess(); - Assert.AreEqual(4, ((IHasComboInformation)converted.HitObjects.ElementAt(0)).BeatmapSkinComboIndex); - Assert.AreEqual(5, ((IHasComboInformation)converted.HitObjects.ElementAt(2)).BeatmapSkinComboIndex); - Assert.AreEqual(5, ((IHasComboInformation)converted.HitObjects.ElementAt(4)).BeatmapSkinComboIndex); - Assert.AreEqual(6, ((IHasComboInformation)converted.HitObjects.ElementAt(6)).BeatmapSkinComboIndex); - Assert.AreEqual(11, ((IHasComboInformation)converted.HitObjects.ElementAt(8)).BeatmapSkinComboIndex); - Assert.AreEqual(14, ((IHasComboInformation)converted.HitObjects.ElementAt(11)).BeatmapSkinComboIndex); + Assert.AreEqual(4, ((IHasComboInformation)converted.HitObjects.ElementAt(0)).ComboIndexWithOffsets); + Assert.AreEqual(5, ((IHasComboInformation)converted.HitObjects.ElementAt(2)).ComboIndexWithOffsets); + Assert.AreEqual(5, ((IHasComboInformation)converted.HitObjects.ElementAt(4)).ComboIndexWithOffsets); + Assert.AreEqual(6, ((IHasComboInformation)converted.HitObjects.ElementAt(6)).ComboIndexWithOffsets); + Assert.AreEqual(11, ((IHasComboInformation)converted.HitObjects.ElementAt(8)).ComboIndexWithOffsets); + Assert.AreEqual(14, ((IHasComboInformation)converted.HitObjects.ElementAt(11)).ComboIndexWithOffsets); } } diff --git a/osu.Game.Tests/Gameplay/TestSceneHitObjectAccentColour.cs b/osu.Game.Tests/Gameplay/TestSceneHitObjectAccentColour.cs index f6f482e254..9d53a5ba63 100644 --- a/osu.Game.Tests/Gameplay/TestSceneHitObjectAccentColour.cs +++ b/osu.Game.Tests/Gameplay/TestSceneHitObjectAccentColour.cs @@ -100,7 +100,7 @@ namespace osu.Game.Tests.Gameplay set => ComboIndexBindable.Value = value; } - public int BeatmapSkinComboIndex { get; set; } + public int ComboIndexWithOffsets { get; set; } public Bindable LastInComboBindable { get; } = new Bindable(); diff --git a/osu.Game/Beatmaps/BeatmapProcessor.cs b/osu.Game/Beatmaps/BeatmapProcessor.cs index 9121222bd7..cdeaab06ed 100644 --- a/osu.Game/Beatmaps/BeatmapProcessor.cs +++ b/osu.Game/Beatmaps/BeatmapProcessor.cs @@ -34,21 +34,19 @@ namespace osu.Game.Beatmaps isFirst = false; } + obj.ComboIndex = lastObj?.ComboIndex ?? 0; + obj.ComboIndexWithOffsets = lastObj?.ComboIndexWithOffsets ?? 0; + obj.IndexInCurrentCombo = (lastObj?.IndexInCurrentCombo + 1) ?? 0; + if (obj.NewCombo) { obj.IndexInCurrentCombo = 0; - obj.ComboIndex = (lastObj?.ComboIndex ?? 0) + 1; - obj.BeatmapSkinComboIndex = (lastObj?.BeatmapSkinComboIndex ?? 0) + obj.ComboOffset + 1; + obj.ComboIndex++; + obj.ComboIndexWithOffsets += obj.ComboOffset + 1; if (lastObj != null) lastObj.LastInCombo = true; } - else if (lastObj != null) - { - obj.IndexInCurrentCombo = lastObj.IndexInCurrentCombo + 1; - obj.ComboIndex = lastObj.ComboIndex; - obj.BeatmapSkinComboIndex = lastObj.BeatmapSkinComboIndex; - } lastObj = obj; } diff --git a/osu.Game/Rulesets/Objects/Types/IHasComboInformation.cs b/osu.Game/Rulesets/Objects/Types/IHasComboInformation.cs index 46d7bef498..f01181f436 100644 --- a/osu.Game/Rulesets/Objects/Types/IHasComboInformation.cs +++ b/osu.Game/Rulesets/Objects/Types/IHasComboInformation.cs @@ -27,10 +27,10 @@ namespace osu.Game.Rulesets.Objects.Types int ComboIndex { get; set; } /// - /// A with the of this and all previous hitobjects applied to it. - /// This is used primarily for beatmap skins during combo colour retrieval, rather than the regular . + /// The offset of this combo in relation to the beatmap, with all aggregate s applied. + /// This should be used instead of only when retrieving combo colours from the beatmap's skin. /// - int BeatmapSkinComboIndex { get; set; } + int ComboIndexWithOffsets { get; set; } /// /// Whether the HitObject starts a new combo. diff --git a/osu.Game/Skinning/LegacyBeatmapSkin.cs b/osu.Game/Skinning/LegacyBeatmapSkin.cs index 5b46a21381..e6ddeba316 100644 --- a/osu.Game/Skinning/LegacyBeatmapSkin.cs +++ b/osu.Game/Skinning/LegacyBeatmapSkin.cs @@ -63,7 +63,7 @@ namespace osu.Game.Skinning } protected override IBindable GetComboColour(IHasComboColours source, int comboIndex, IHasComboInformation combo) - => base.GetComboColour(source, combo.BeatmapSkinComboIndex, combo); + => base.GetComboColour(source, combo.ComboIndexWithOffsets, combo); public override ISample GetSample(ISampleInfo sampleInfo) { From cd7b90363afe3b39d923a70c054472a502d3f76d Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 22 Jul 2021 16:15:23 +0300 Subject: [PATCH 13/19] Check nested hitobjects while asserting accent colour --- .../TestSceneLegacyBeatmapSkin.cs | 46 ++++++++++++------- 1 file changed, 30 insertions(+), 16 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneLegacyBeatmapSkin.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneLegacyBeatmapSkin.cs index f2f68e805c..f6484886da 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneLegacyBeatmapSkin.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneLegacyBeatmapSkin.cs @@ -9,6 +9,8 @@ using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Game.Beatmaps; using osu.Game.Configuration; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Skinning; using osu.Game.Tests.Beatmaps; @@ -113,31 +115,43 @@ namespace osu.Game.Rulesets.Osu.Tests { int index = 0; - foreach (var drawable in TestPlayer.DrawableRuleset.Playfield.AllHitObjects) + return TestPlayer.DrawableRuleset.Playfield.AllHitObjects.All(d => { - index = nextExpectedComboIndex(index, (OsuHitObject)drawable.HitObject); - - if (drawable.AccentColour.Value != expectedColours[index % expectedColours.Length]) - return false; - } - - return true; + index = nextExpectedComboIndex(index, (OsuHitObject)d.HitObject); + return checkComboColour(d, expectedColours[index % expectedColours.Length]); + }); }); + + static bool checkComboColour(DrawableHitObject drawableHitObject, Color4 expectedColour) + { + return drawableHitObject.AccentColour.Value == expectedColour && + drawableHitObject.NestedHitObjects.All(n => checkComboColour(n, expectedColour)); + } } private static IEnumerable getHitCirclesWithLegacyOffsets() { var hitObjects = new List(); - for (int i = 0; i < 5; i++) + for (int i = 0; i < 10; i++) { - hitObjects.Add(new HitCircle - { - StartTime = i, - Position = new Vector2(256, 192), - NewCombo = true, - ComboOffset = i, - }); + var hitObject = i % 2 == 0 + ? (OsuHitObject)new HitCircle() + : new Slider() + { + Path = new SliderPath(new[] + { + new PathControlPoint(new Vector2(0, 0)), + new PathControlPoint(new Vector2(100, 0)), + }) + }; + + hitObject.StartTime = i; + hitObject.Position = new Vector2(256, 192); + hitObject.NewCombo = true; + hitObject.ComboOffset = i; + + hitObjects.Add(hitObject); } return hitObjects; From 523c154f153f0bb2e692a7048d68e3977f8dfdfa Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 22 Jul 2021 16:22:42 +0300 Subject: [PATCH 14/19] Add `ComboIndexWithOffsetsBindable` and bind similar to `ComboIndexBindable` --- osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs | 8 +++++++- osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs | 8 +++++++- .../Gameplay/TestSceneHitObjectAccentColour.cs | 8 +++++++- .../Rulesets/Objects/Drawables/DrawableHitObject.cs | 11 +++++++++++ osu.Game/Rulesets/Objects/HitObject.cs | 1 + .../Rulesets/Objects/Types/IHasComboInformation.cs | 2 ++ .../Components/Timeline/TimelineHitObjectBlueprint.cs | 6 ++++++ 7 files changed, 41 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs b/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs index 33353ec423..d43e6f1c8b 100644 --- a/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs +++ b/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs @@ -95,7 +95,13 @@ namespace osu.Game.Rulesets.Catch.Objects set => ComboIndexBindable.Value = value; } - public int ComboIndexWithOffsets { get; set; } + public Bindable ComboIndexWithOffsetsBindable { get; } = new Bindable(); + + public int ComboIndexWithOffsets + { + get => ComboIndexWithOffsetsBindable.Value; + set => ComboIndexWithOffsetsBindable.Value = value; + } public Bindable LastInComboBindable { get; } = new Bindable(); diff --git a/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs b/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs index 7c60480b00..36629fa41e 100644 --- a/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs +++ b/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs @@ -97,7 +97,13 @@ namespace osu.Game.Rulesets.Osu.Objects set => ComboIndexBindable.Value = value; } - public int ComboIndexWithOffsets { get; set; } + public Bindable ComboIndexWithOffsetsBindable { get; } = new Bindable(); + + public int ComboIndexWithOffsets + { + get => ComboIndexWithOffsetsBindable.Value; + set => ComboIndexWithOffsetsBindable.Value = value; + } public Bindable LastInComboBindable { get; } = new Bindable(); diff --git a/osu.Game.Tests/Gameplay/TestSceneHitObjectAccentColour.cs b/osu.Game.Tests/Gameplay/TestSceneHitObjectAccentColour.cs index 9d53a5ba63..34f70659e3 100644 --- a/osu.Game.Tests/Gameplay/TestSceneHitObjectAccentColour.cs +++ b/osu.Game.Tests/Gameplay/TestSceneHitObjectAccentColour.cs @@ -100,7 +100,13 @@ namespace osu.Game.Tests.Gameplay set => ComboIndexBindable.Value = value; } - public int ComboIndexWithOffsets { get; set; } + public Bindable ComboIndexWithOffsetsBindable { get; } = new Bindable(); + + public int ComboIndexWithOffsets + { + get => ComboIndexWithOffsetsBindable.Value; + set => ComboIndexWithOffsetsBindable.Value = value; + } public Bindable LastInComboBindable { get; } = new Bindable(); diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index 69bdb5fd73..2b222371e2 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -124,7 +124,9 @@ namespace osu.Game.Rulesets.Objects.Drawables public readonly Bindable StartTimeBindable = new Bindable(); private readonly BindableList samplesBindable = new BindableList(); private readonly Bindable userPositionalHitSounds = new Bindable(); + private readonly Bindable comboIndexBindable = new Bindable(); + private readonly Bindable comboIndexWithOffsetsBindable = new Bindable(); protected override bool RequiresChildrenUpdate => true; @@ -186,6 +188,7 @@ namespace osu.Game.Rulesets.Objects.Drawables base.LoadComplete(); comboIndexBindable.BindValueChanged(_ => UpdateComboColour(), true); + comboIndexWithOffsetsBindable.BindValueChanged(_ => UpdateComboColour(), true); updateState(ArmedState.Idle, true); } @@ -250,7 +253,10 @@ namespace osu.Game.Rulesets.Objects.Drawables StartTimeBindable.BindValueChanged(onStartTimeChanged); if (HitObject is IHasComboInformation combo) + { comboIndexBindable.BindTo(combo.ComboIndexBindable); + comboIndexWithOffsetsBindable.BindTo(combo.ComboIndexWithOffsetsBindable); + } samplesBindable.BindTo(HitObject.SamplesBindable); samplesBindable.BindCollectionChanged(onSamplesChanged, true); @@ -275,8 +281,13 @@ namespace osu.Game.Rulesets.Objects.Drawables protected sealed override void OnFree(HitObjectLifetimeEntry entry) { StartTimeBindable.UnbindFrom(HitObject.StartTimeBindable); + if (HitObject is IHasComboInformation combo) + { comboIndexBindable.UnbindFrom(combo.ComboIndexBindable); + comboIndexWithOffsetsBindable.UnbindFrom(combo.ComboIndexWithOffsetsBindable); + } + samplesBindable.UnbindFrom(HitObject.SamplesBindable); // Changes in start time trigger state updates. When a new hitobject is applied, OnApply() automatically performs a state update anyway. diff --git a/osu.Game/Rulesets/Objects/HitObject.cs b/osu.Game/Rulesets/Objects/HitObject.cs index db02eafa92..422655502d 100644 --- a/osu.Game/Rulesets/Objects/HitObject.cs +++ b/osu.Game/Rulesets/Objects/HitObject.cs @@ -118,6 +118,7 @@ namespace osu.Game.Rulesets.Objects foreach (var n in NestedHitObjects.OfType()) { n.ComboIndexBindable.BindTo(hasCombo.ComboIndexBindable); + n.ComboIndexWithOffsetsBindable.BindTo(hasCombo.ComboIndexWithOffsetsBindable); n.IndexInCurrentComboBindable.BindTo(hasCombo.IndexInCurrentComboBindable); } } diff --git a/osu.Game/Rulesets/Objects/Types/IHasComboInformation.cs b/osu.Game/Rulesets/Objects/Types/IHasComboInformation.cs index f01181f436..eddf764ae7 100644 --- a/osu.Game/Rulesets/Objects/Types/IHasComboInformation.cs +++ b/osu.Game/Rulesets/Objects/Types/IHasComboInformation.cs @@ -26,6 +26,8 @@ namespace osu.Game.Rulesets.Objects.Types /// int ComboIndex { get; set; } + Bindable ComboIndexWithOffsetsBindable { get; } + /// /// The offset of this combo in relation to the beatmap, with all aggregate s applied. /// This should be used instead of only when retrieving combo colours from the beatmap's skin. diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs index 7f8cc1c8fa..d115c10b5d 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs @@ -37,7 +37,10 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline private readonly Bindable startTime; private Bindable indexInCurrentComboBindable; + private Bindable comboIndexBindable; + private Bindable comboIndexWithOffsetsBindable; + private Bindable displayColourBindable; private readonly ExtendableCircle circle; @@ -122,6 +125,9 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline comboIndexBindable = comboInfo.ComboIndexBindable.GetBoundCopy(); comboIndexBindable.BindValueChanged(_ => updateColour(), true); + comboIndexWithOffsetsBindable = comboInfo.ComboIndexWithOffsetsBindable.GetBoundCopy(); + comboIndexWithOffsetsBindable.BindValueChanged(_ => updateColour(), true); + skin.SourceChanged += updateColour; break; } From 20adfabc975f39c7afbbd1ae622612a01395392b Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 22 Jul 2021 16:42:00 +0300 Subject: [PATCH 15/19] Remove unnecessary parentheses I... forgot to amend that. --- osu.Game.Rulesets.Osu.Tests/TestSceneLegacyBeatmapSkin.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneLegacyBeatmapSkin.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneLegacyBeatmapSkin.cs index f6484886da..8b51225e98 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneLegacyBeatmapSkin.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneLegacyBeatmapSkin.cs @@ -137,7 +137,7 @@ namespace osu.Game.Rulesets.Osu.Tests { var hitObject = i % 2 == 0 ? (OsuHitObject)new HitCircle() - : new Slider() + : new Slider { Path = new SliderPath(new[] { From ee3791ccf2cea6710d68dab0412f15b620a38f86 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 23 Jul 2021 06:24:58 +0300 Subject: [PATCH 16/19] Update colours once on `TimelineHitObjectBlueprint` --- .../Components/Timeline/TimelineHitObjectBlueprint.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs index d115c10b5d..6b75696d23 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs @@ -123,12 +123,12 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline indexInCurrentComboBindable.BindValueChanged(_ => updateComboIndex(), true); comboIndexBindable = comboInfo.ComboIndexBindable.GetBoundCopy(); - comboIndexBindable.BindValueChanged(_ => updateColour(), true); - comboIndexWithOffsetsBindable = comboInfo.ComboIndexWithOffsetsBindable.GetBoundCopy(); - comboIndexWithOffsetsBindable.BindValueChanged(_ => updateColour(), true); + comboIndexBindable.ValueChanged += _ => updateColour(); + comboIndexWithOffsetsBindable.ValueChanged += _ => updateColour(); skin.SourceChanged += updateColour; + updateColour(); break; } } From 8600a3bf5b3fc057ce0d342e9c4c1df88501b946 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 23 Jul 2021 07:15:36 +0300 Subject: [PATCH 17/19] Replace "offset" term in combo index documentations with "index" instead --- osu.Game/Rulesets/Objects/Types/IHasComboInformation.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Rulesets/Objects/Types/IHasComboInformation.cs b/osu.Game/Rulesets/Objects/Types/IHasComboInformation.cs index eddf764ae7..29a56fc625 100644 --- a/osu.Game/Rulesets/Objects/Types/IHasComboInformation.cs +++ b/osu.Game/Rulesets/Objects/Types/IHasComboInformation.cs @@ -15,21 +15,21 @@ namespace osu.Game.Rulesets.Objects.Types Bindable IndexInCurrentComboBindable { get; } /// - /// The offset of this hitobject in the current combo. + /// The index of this hitobject in the current combo. /// int IndexInCurrentCombo { get; set; } Bindable ComboIndexBindable { get; } /// - /// The offset of this combo in relation to the beatmap. + /// The index of this combo in relation to the beatmap. /// int ComboIndex { get; set; } Bindable ComboIndexWithOffsetsBindable { get; } /// - /// The offset of this combo in relation to the beatmap, with all aggregate s applied. + /// The index of this combo in relation to the beatmap, with all aggregate s applied. /// This should be used instead of only when retrieving combo colours from the beatmap's skin. /// int ComboIndexWithOffsets { get; set; } From 0b3b9e35baa308c5d7778a41c149534e025fc4b2 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 23 Jul 2021 07:32:56 +0300 Subject: [PATCH 18/19] Also update colours once on `DrawableHitObject` --- osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index 2b222371e2..25f3b8931a 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -187,7 +187,7 @@ namespace osu.Game.Rulesets.Objects.Drawables { base.LoadComplete(); - comboIndexBindable.BindValueChanged(_ => UpdateComboColour(), true); + comboIndexBindable.BindValueChanged(_ => UpdateComboColour()); comboIndexWithOffsetsBindable.BindValueChanged(_ => UpdateComboColour(), true); updateState(ArmedState.Idle, true); From 7bc30b46ff311c3d1fc1d585dc26ddd00c4098e1 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 23 Jul 2021 07:51:58 +0300 Subject: [PATCH 19/19] Use `BindValueChanged` with last running immediately instead --- .../Components/Timeline/TimelineHitObjectBlueprint.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs index 6b75696d23..6e57b8e88c 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs @@ -125,10 +125,10 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline comboIndexBindable = comboInfo.ComboIndexBindable.GetBoundCopy(); comboIndexWithOffsetsBindable = comboInfo.ComboIndexWithOffsetsBindable.GetBoundCopy(); - comboIndexBindable.ValueChanged += _ => updateColour(); - comboIndexWithOffsetsBindable.ValueChanged += _ => updateColour(); + comboIndexBindable.BindValueChanged(_ => updateColour()); + comboIndexWithOffsetsBindable.BindValueChanged(_ => updateColour(), true); + skin.SourceChanged += updateColour; - updateColour(); break; } }