diff --git a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs index 68a8dfb7d3..15d4edc411 100644 --- a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs +++ b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs @@ -41,6 +41,7 @@ namespace osu.Game.Rulesets.Catch.Beatmaps RepeatCount = curveData.RepeatCount, X = (positionData?.X ?? 0) / CatchPlayfield.BASE_WIDTH, NewCombo = comboData?.NewCombo ?? false, + ComboOffset = comboData?.ComboOffset ?? 0, LegacyLastTickOffset = legacyOffset?.LegacyLastTickOffset ?? 0 }; } @@ -51,7 +52,8 @@ namespace osu.Game.Rulesets.Catch.Beatmaps StartTime = obj.StartTime, Samples = obj.Samples, Duration = endTime.Duration, - NewCombo = comboData?.NewCombo ?? false + NewCombo = comboData?.NewCombo ?? false, + ComboOffset = comboData?.ComboOffset ?? 0, }; } else @@ -61,6 +63,7 @@ namespace osu.Game.Rulesets.Catch.Beatmaps StartTime = obj.StartTime, Samples = obj.Samples, NewCombo = comboData?.NewCombo ?? false, + ComboOffset = comboData?.ComboOffset ?? 0, X = (positionData?.X ?? 0) / CatchPlayfield.BASE_WIDTH }; } diff --git a/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs b/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs index d55cdac115..621fc100c2 100644 --- a/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs +++ b/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs @@ -20,6 +20,8 @@ namespace osu.Game.Rulesets.Catch.Objects public virtual bool NewCombo { get; set; } + public int ComboOffset { get; set; } + public int IndexInCurrentCombo { get; set; } public int ComboIndex { get; set; } diff --git a/osu.Game.Rulesets.Mania/Objects/HoldNote.cs b/osu.Game.Rulesets.Mania/Objects/HoldNote.cs index e493956d6e..77562bb4c2 100644 --- a/osu.Game.Rulesets.Mania/Objects/HoldNote.cs +++ b/osu.Game.Rulesets.Mania/Objects/HoldNote.cs @@ -70,9 +70,6 @@ namespace osu.Game.Rulesets.Mania.Objects TimingControlPoint timingPoint = controlPointInfo.TimingPointAt(StartTime); tickSpacing = timingPoint.BeatLength / difficulty.SliderTickRate; - - Head.ApplyDefaults(controlPointInfo, difficulty); - Tail.ApplyDefaults(controlPointInfo, difficulty); } protected override void CreateNestedHitObjects() @@ -80,6 +77,9 @@ namespace osu.Game.Rulesets.Mania.Objects base.CreateNestedHitObjects(); createTicks(); + + AddNested(Head); + AddNested(Tail); } private void createTicks() diff --git a/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapConverter.cs b/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapConverter.cs index 405493cde4..93f3f06dc2 100644 --- a/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapConverter.cs +++ b/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapConverter.cs @@ -42,6 +42,7 @@ namespace osu.Game.Rulesets.Osu.Beatmaps RepeatCount = curveData.RepeatCount, Position = positionData?.Position ?? Vector2.Zero, NewCombo = comboData?.NewCombo ?? false, + ComboOffset = comboData?.ComboOffset ?? 0, LegacyLastTickOffset = legacyOffset?.LegacyLastTickOffset }; } @@ -52,7 +53,9 @@ namespace osu.Game.Rulesets.Osu.Beatmaps StartTime = original.StartTime, Samples = original.Samples, EndTime = endTimeData.EndTime, - Position = positionData?.Position ?? OsuPlayfield.BASE_SIZE / 2 + Position = positionData?.Position ?? OsuPlayfield.BASE_SIZE / 2, + NewCombo = comboData?.NewCombo ?? false, + ComboOffset = comboData?.ComboOffset ?? 0, }; } else @@ -62,7 +65,8 @@ namespace osu.Game.Rulesets.Osu.Beatmaps StartTime = original.StartTime, Samples = original.Samples, Position = positionData?.Position ?? Vector2.Zero, - NewCombo = comboData?.NewCombo ?? false + NewCombo = comboData?.NewCombo ?? false, + ComboOffset = comboData?.ComboOffset ?? 0, }; } } diff --git a/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs b/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs index 48a6365c00..fdf5aaffa8 100644 --- a/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs +++ b/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs @@ -54,6 +54,8 @@ namespace osu.Game.Rulesets.Osu.Objects public virtual bool NewCombo { get; set; } + public int ComboOffset { get; set; } + public virtual int IndexInCurrentCombo { get; set; } public virtual int ComboIndex { get; set; } diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs index 400380b407..0a5df0e093 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs @@ -186,6 +186,18 @@ namespace osu.Game.Tests.Beatmaps.Formats } } + [Test] + public void TestDecodeBeatmapComboOffsets() + { + var decoder = new LegacyBeatmapDecoder(); + using (var resStream = Resource.OpenResource("hitobject-combo-offset.osu")) + using (var stream = new StreamReader(resStream)) + { + var beatmap = decoder.Decode(stream); + Assert.AreEqual(3, ((IHasCombo)beatmap.HitObjects[0]).ComboOffset); + } + } + [Test] public void TestDecodeBeatmapHitObjects() { diff --git a/osu.Game.Tests/Resources/hitobject-combo-offset.osu b/osu.Game.Tests/Resources/hitobject-combo-offset.osu new file mode 100644 index 0000000000..4a44d31e22 --- /dev/null +++ b/osu.Game.Tests/Resources/hitobject-combo-offset.osu @@ -0,0 +1,4 @@ +osu file format v14 + +[HitObjects] +255,193,2170,49,0,0:0:0:0: \ No newline at end of file diff --git a/osu.Game/Beatmaps/BeatmapProcessor.cs b/osu.Game/Beatmaps/BeatmapProcessor.cs index 0173125e8b..9d7cd673dc 100644 --- a/osu.Game/Beatmaps/BeatmapProcessor.cs +++ b/osu.Game/Beatmaps/BeatmapProcessor.cs @@ -27,11 +27,10 @@ namespace osu.Game.Beatmaps if (obj.NewCombo) { obj.IndexInCurrentCombo = 0; + obj.ComboIndex = (lastObj?.ComboIndex ?? 0) + obj.ComboOffset + 1; + if (lastObj != null) - { lastObj.LastInCombo = true; - obj.ComboIndex = lastObj.ComboIndex + 1; - } } else if (lastObj != null) { diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs index 29be751de2..31a7698f50 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs @@ -126,16 +126,16 @@ namespace osu.Game.Beatmaps.Formats switch (beatmap.BeatmapInfo.RulesetID) { case 0: - parser = new Rulesets.Objects.Legacy.Osu.ConvertHitObjectParser(); + parser = new Rulesets.Objects.Legacy.Osu.ConvertHitObjectParser(getOffsetTime(), FormatVersion); break; case 1: - parser = new Rulesets.Objects.Legacy.Taiko.ConvertHitObjectParser(); + parser = new Rulesets.Objects.Legacy.Taiko.ConvertHitObjectParser(getOffsetTime(), FormatVersion); break; case 2: - parser = new Rulesets.Objects.Legacy.Catch.ConvertHitObjectParser(); + parser = new Rulesets.Objects.Legacy.Catch.ConvertHitObjectParser(getOffsetTime(), FormatVersion); break; case 3: - parser = new Rulesets.Objects.Legacy.Mania.ConvertHitObjectParser(); + parser = new Rulesets.Objects.Legacy.Mania.ConvertHitObjectParser(getOffsetTime(), FormatVersion); break; } @@ -405,9 +405,9 @@ namespace osu.Game.Beatmaps.Formats { // If the ruleset wasn't specified, assume the osu!standard ruleset. if (parser == null) - parser = new Rulesets.Objects.Legacy.Osu.ConvertHitObjectParser(); + parser = new Rulesets.Objects.Legacy.Osu.ConvertHitObjectParser(getOffsetTime(), FormatVersion); - var obj = parser.Parse(line, getOffsetTime()); + var obj = parser.Parse(line); if (obj != null) { diff --git a/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertHit.cs b/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertHit.cs index 50035ea116..0573a08361 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertHit.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertHit.cs @@ -13,5 +13,7 @@ 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 c7451dc978..fb4cde479b 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertHitObjectParser.cs @@ -13,21 +13,28 @@ namespace osu.Game.Rulesets.Objects.Legacy.Catch /// public class ConvertHitObjectParser : Legacy.ConvertHitObjectParser { - protected override HitObject CreateHit(Vector2 position, bool newCombo) + public ConvertHitObjectParser(double offset, int formatVersion) + : base(offset, formatVersion) + { + } + + protected override HitObject CreateHit(Vector2 position, bool newCombo, int comboOffset) { return new ConvertHit { X = position.X, NewCombo = newCombo, + ComboOffset = comboOffset }; } - protected override HitObject CreateSlider(Vector2 position, bool newCombo, List controlPoints, double length, CurveType curveType, int repeatCount, List> repeatSamples) + protected override HitObject CreateSlider(Vector2 position, bool newCombo, int comboOffset, List controlPoints, double length, CurveType curveType, int repeatCount, List> repeatSamples) { return new ConvertSlider { X = position.X, - NewCombo = newCombo, + NewCombo = FirstObject || newCombo, + ComboOffset = comboOffset, ControlPoints = controlPoints, Distance = length, CurveType = curveType, @@ -36,15 +43,17 @@ namespace osu.Game.Rulesets.Objects.Legacy.Catch }; } - protected override HitObject CreateSpinner(Vector2 position, double endTime) + protected override HitObject CreateSpinner(Vector2 position, bool newCombo, int comboOffset, double endTime) { return new ConvertSpinner { - EndTime = endTime + EndTime = endTime, + NewCombo = FirstObject || newCombo, + ComboOffset = comboOffset }; } - protected override HitObject CreateHold(Vector2 position, bool newCombo, double endTime) + protected override HitObject CreateHold(Vector2 position, bool newCombo, int comboOffset, double endTime) { return null; } diff --git a/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertSlider.cs b/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertSlider.cs index 73e277a125..a187caaa26 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertSlider.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertSlider.cs @@ -13,5 +13,7 @@ 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 9dfe12f25e..db79ca60f1 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertSpinner.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertSpinner.cs @@ -8,10 +8,14 @@ namespace osu.Game.Rulesets.Objects.Legacy.Catch /// /// Legacy osu!catch Spinner-type, used for parsing Beatmaps. /// - internal sealed class ConvertSpinner : HitObject, IHasEndTime + internal sealed class ConvertSpinner : HitObject, IHasEndTime, IHasCombo { public double EndTime { get; set; } public double Duration => EndTime - StartTime; + + public bool NewCombo { get; set; } + + public int ComboOffset { get; set; } } } diff --git a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs index c48060bfa9..8236333a3f 100644 --- a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs @@ -19,12 +19,25 @@ namespace osu.Game.Rulesets.Objects.Legacy /// public abstract class ConvertHitObjectParser : HitObjectParser { - public override HitObject Parse(string text) + /// + /// The offset to apply to all time values. + /// + protected readonly double Offset; + + /// + /// The beatmap version. + /// + protected readonly int FormatVersion; + + protected bool FirstObject { get; private set; } = true; + + protected ConvertHitObjectParser(double offset, int formatVersion) { - return Parse(text, 0); + Offset = offset; + FormatVersion = formatVersion; } - public HitObject Parse(string text, double offset) + public override HitObject Parse(string text) { try { @@ -32,7 +45,11 @@ namespace osu.Game.Rulesets.Objects.Legacy Vector2 pos = new Vector2((int)Convert.ToSingle(split[0], CultureInfo.InvariantCulture), (int)Convert.ToSingle(split[1], CultureInfo.InvariantCulture)); - ConvertHitObjectType type = (ConvertHitObjectType)int.Parse(split[3]) & ~ConvertHitObjectType.ColourHax; + ConvertHitObjectType type = (ConvertHitObjectType)int.Parse(split[3]); + + int comboOffset = (int)(type & ConvertHitObjectType.ComboOffset) >> 4; + type &= ~ConvertHitObjectType.ComboOffset; + bool combo = type.HasFlag(ConvertHitObjectType.NewCombo); type &= ~ConvertHitObjectType.NewCombo; @@ -43,7 +60,7 @@ namespace osu.Game.Rulesets.Objects.Legacy if (type.HasFlag(ConvertHitObjectType.Circle)) { - result = CreateHit(pos, combo); + result = CreateHit(pos, combo, comboOffset); if (split.Length > 5) readCustomSampleBanks(split[5], bankInfo); @@ -148,11 +165,11 @@ namespace osu.Game.Rulesets.Objects.Legacy for (int i = 0; i < nodes; i++) nodeSamples.Add(convertSoundType(nodeSoundTypes[i], nodeBankInfos[i])); - result = CreateSlider(pos, combo, points, length, curveType, repeatCount, nodeSamples); + result = CreateSlider(pos, combo, comboOffset, points, length, curveType, repeatCount, nodeSamples); } else if (type.HasFlag(ConvertHitObjectType.Spinner)) { - result = CreateSpinner(new Vector2(512, 384) / 2, Convert.ToDouble(split[5], CultureInfo.InvariantCulture) + offset); + result = CreateSpinner(new Vector2(512, 384) / 2, combo, comboOffset, Convert.ToDouble(split[5], CultureInfo.InvariantCulture) + Offset); if (split.Length > 6) readCustomSampleBanks(split[6], bankInfo); @@ -170,15 +187,17 @@ namespace osu.Game.Rulesets.Objects.Legacy readCustomSampleBanks(string.Join(":", ss.Skip(1)), bankInfo); } - result = CreateHold(pos, combo, endTime + offset); + result = CreateHold(pos, combo, comboOffset, endTime + Offset); } if (result == null) throw new InvalidOperationException($@"Unknown hit object type {type}."); - result.StartTime = Convert.ToDouble(split[2], CultureInfo.InvariantCulture) + offset; + result.StartTime = Convert.ToDouble(split[2], CultureInfo.InvariantCulture) + Offset; result.Samples = convertSoundType(soundType, bankInfo); + FirstObject = false; + return result; } catch (FormatException) @@ -221,37 +240,42 @@ namespace osu.Game.Rulesets.Objects.Legacy /// /// The position of the hit object. /// Whether the hit object creates a new combo. + /// When starting a new combo, the offset of the new combo relative to the current one. /// The hit object. - protected abstract HitObject CreateHit(Vector2 position, bool newCombo); + protected abstract HitObject CreateHit(Vector2 position, bool newCombo, int comboOffset); /// /// Creats a legacy Slider-type hit object. /// /// The position of the hit object. /// Whether the hit object creates a new combo. + /// When starting a new combo, the offset of the new combo relative to the current one. /// The slider control points. /// The slider length. /// The slider curve type. /// The slider repeat count. /// The samples to be played when the repeat nodes are hit. This includes the head and tail of the slider. /// The hit object. - protected abstract HitObject CreateSlider(Vector2 position, bool newCombo, List controlPoints, double length, CurveType curveType, int repeatCount, List> repeatSamples); + protected abstract HitObject CreateSlider(Vector2 position, bool newCombo, int comboOffset, List controlPoints, double length, CurveType curveType, int repeatCount, List> repeatSamples); /// /// Creates a legacy Spinner-type hit object. /// /// The position of the hit object. + /// Whether the hit object creates a new combo. + /// When starting a new combo, the offset of the new combo relative to the current one. /// The spinner end time. /// The hit object. - protected abstract HitObject CreateSpinner(Vector2 position, double endTime); + protected abstract HitObject CreateSpinner(Vector2 position, bool newCombo, int comboOffset, double endTime); /// /// Creates a legacy Hold-type hit object. /// /// The position of the hit object. /// Whether the hit object creates a new combo. + /// When starting a new combo, the offset of the new combo relative to the current one. /// The hold end time. - protected abstract HitObject CreateHold(Vector2 position, bool newCombo, double endTime); + protected abstract HitObject CreateHold(Vector2 position, bool newCombo, int comboOffset, double endTime); private List convertSoundType(LegacySoundType type, SampleBankInfo bankInfo) { diff --git a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectType.cs b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectType.cs index c0626c3e56..fa47e56de7 100644 --- a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectType.cs +++ b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectType.cs @@ -12,7 +12,7 @@ namespace osu.Game.Rulesets.Objects.Legacy Slider = 1 << 1, NewCombo = 1 << 2, Spinner = 1 << 3, - ColourHax = 112, + ComboOffset = 1 << 4 | 1 << 5 | 1 << 6, Hold = 1 << 7 } } diff --git a/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHit.cs b/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHit.cs index ea4e7f6907..cbc8d2d4df 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHit.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHit.cs @@ -8,12 +8,10 @@ namespace osu.Game.Rulesets.Objects.Legacy.Mania /// /// Legacy osu!mania Hit-type, used for parsing Beatmaps. /// - internal sealed class ConvertHit : HitObject, IHasXPosition, IHasCombo + internal sealed class ConvertHit : HitObject, IHasXPosition { public float X { get; set; } - public bool NewCombo { get; set; } - protected override HitWindows CreateHitWindows() => null; } } diff --git a/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHitObjectParser.cs index 99ba1304e8..6f59965e18 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHitObjectParser.cs @@ -13,21 +13,24 @@ namespace osu.Game.Rulesets.Objects.Legacy.Mania /// public class ConvertHitObjectParser : Legacy.ConvertHitObjectParser { - protected override HitObject CreateHit(Vector2 position, bool newCombo) + public ConvertHitObjectParser(double offset, int formatVersion) + : base(offset, formatVersion) + { + } + + protected override HitObject CreateHit(Vector2 position, bool newCombo, int comboOffset) { return new ConvertHit { - X = position.X, - NewCombo = newCombo, + X = position.X }; } - protected override HitObject CreateSlider(Vector2 position, bool newCombo, List controlPoints, double length, CurveType curveType, int repeatCount, List> repeatSamples) + protected override HitObject CreateSlider(Vector2 position, bool newCombo, int comboOffset, List controlPoints, double length, CurveType curveType, int repeatCount, List> repeatSamples) { return new ConvertSlider { X = position.X, - NewCombo = newCombo, ControlPoints = controlPoints, Distance = length, CurveType = curveType, @@ -36,7 +39,7 @@ namespace osu.Game.Rulesets.Objects.Legacy.Mania }; } - protected override HitObject CreateSpinner(Vector2 position, double endTime) + protected override HitObject CreateSpinner(Vector2 position, bool newCombo, int comboOffset, double endTime) { return new ConvertSpinner { @@ -45,7 +48,7 @@ namespace osu.Game.Rulesets.Objects.Legacy.Mania }; } - protected override HitObject CreateHold(Vector2 position, bool newCombo, double endTime) + protected override HitObject CreateHold(Vector2 position, bool newCombo, int comboOffset, double endTime) { return new ConvertHold { diff --git a/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertSlider.cs b/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertSlider.cs index a8d7b23df1..e1572889a3 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertSlider.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertSlider.cs @@ -8,12 +8,10 @@ namespace osu.Game.Rulesets.Objects.Legacy.Mania /// /// Legacy osu!mania Slider-type, used for parsing Beatmaps. /// - internal sealed class ConvertSlider : Legacy.ConvertSlider, IHasXPosition, IHasCombo + internal sealed class ConvertSlider : Legacy.ConvertSlider, IHasXPosition { public float X { get; set; } - public bool NewCombo { get; set; } - protected override HitWindows CreateHitWindows() => null; } } diff --git a/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertHit.cs b/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertHit.cs index f015272b2c..0062e83a28 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertHit.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertHit.cs @@ -19,6 +19,8 @@ namespace osu.Game.Rulesets.Objects.Legacy.Osu public bool NewCombo { get; set; } + public int ComboOffset { get; set; } + protected override HitWindows CreateHitWindows() => null; } } diff --git a/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertHitObjectParser.cs index 801e4ea449..0823653830 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertHitObjectParser.cs @@ -14,21 +14,28 @@ namespace osu.Game.Rulesets.Objects.Legacy.Osu /// public class ConvertHitObjectParser : Legacy.ConvertHitObjectParser { - protected override HitObject CreateHit(Vector2 position, bool newCombo) + public ConvertHitObjectParser(double offset, int formatVersion) + : base(offset, formatVersion) + { + } + + protected override HitObject CreateHit(Vector2 position, bool newCombo, int comboOffset) { return new ConvertHit { Position = position, - NewCombo = newCombo, + NewCombo = FirstObject || newCombo, + ComboOffset = comboOffset }; } - protected override HitObject CreateSlider(Vector2 position, bool newCombo, List controlPoints, double length, CurveType curveType, int repeatCount, List> repeatSamples) + protected override HitObject CreateSlider(Vector2 position, bool newCombo, int comboOffset, List controlPoints, double length, CurveType curveType, int repeatCount, List> repeatSamples) { return new ConvertSlider { Position = position, - NewCombo = newCombo, + NewCombo = FirstObject || newCombo, + ComboOffset = comboOffset, ControlPoints = controlPoints, Distance = Math.Max(0, length), CurveType = curveType, @@ -37,16 +44,18 @@ namespace osu.Game.Rulesets.Objects.Legacy.Osu }; } - protected override HitObject CreateSpinner(Vector2 position, double endTime) + protected override HitObject CreateSpinner(Vector2 position, bool newCombo, int comboOffset, double endTime) { return new ConvertSpinner { Position = position, - EndTime = endTime + EndTime = endTime, + NewCombo = FormatVersion <= 8 || FirstObject || newCombo, + ComboOffset = comboOffset }; } - protected override HitObject CreateHold(Vector2 position, bool newCombo, double endTime) + protected override HitObject CreateHold(Vector2 position, bool newCombo, int comboOffset, double endTime) { return null; } diff --git a/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertSlider.cs b/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertSlider.cs index ec5a002bbb..45f7bc9e67 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertSlider.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertSlider.cs @@ -19,6 +19,8 @@ namespace osu.Game.Rulesets.Objects.Legacy.Osu public bool NewCombo { get; set; } + public int ComboOffset { get; set; } + protected override HitWindows CreateHitWindows() => null; } } diff --git a/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertSpinner.cs b/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertSpinner.cs index 0141785f31..3b349d9704 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 : HitObject, IHasEndTime, IHasPosition + internal sealed class ConvertSpinner : HitObject, IHasEndTime, IHasPosition, IHasCombo { public double EndTime { get; set; } @@ -22,5 +22,9 @@ namespace osu.Game.Rulesets.Objects.Legacy.Osu public float Y => Position.Y; protected override HitWindows CreateHitWindows() => null; + + public bool NewCombo { get; set; } + + public int ComboOffset { get; set; } } } diff --git a/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertHit.cs b/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertHit.cs index 5e9786c84a..66e504bf22 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertHit.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertHit.cs @@ -1,17 +1,13 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using osu.Game.Rulesets.Objects.Types; - namespace osu.Game.Rulesets.Objects.Legacy.Taiko { /// /// Legacy osu!taiko Hit-type, used for parsing Beatmaps. /// - internal sealed class ConvertHit : HitObject, IHasCombo + internal sealed class ConvertHit : HitObject { - public bool NewCombo { get; set; } - protected override HitWindows CreateHitWindows() => null; } } diff --git a/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertHitObjectParser.cs index 03b1a3187a..e5904825c2 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertHitObjectParser.cs @@ -13,19 +13,20 @@ namespace osu.Game.Rulesets.Objects.Legacy.Taiko /// public class ConvertHitObjectParser : Legacy.ConvertHitObjectParser { - protected override HitObject CreateHit(Vector2 position, bool newCombo) + public ConvertHitObjectParser(double offset, int formatVersion) + : base(offset, formatVersion) { - return new ConvertHit - { - NewCombo = newCombo, - }; } - protected override HitObject CreateSlider(Vector2 position, bool newCombo, List controlPoints, double length, CurveType curveType, int repeatCount, List> repeatSamples) + protected override HitObject CreateHit(Vector2 position, bool newCombo, int comboOffset) + { + return new ConvertHit(); + } + + protected override HitObject CreateSlider(Vector2 position, bool newCombo, int comboOffset, List controlPoints, double length, CurveType curveType, int repeatCount, List> repeatSamples) { return new ConvertSlider { - NewCombo = newCombo, ControlPoints = controlPoints, Distance = length, CurveType = curveType, @@ -34,7 +35,7 @@ namespace osu.Game.Rulesets.Objects.Legacy.Taiko }; } - protected override HitObject CreateSpinner(Vector2 position, double endTime) + protected override HitObject CreateSpinner(Vector2 position, bool newCombo, int comboOffset, double endTime) { return new ConvertSpinner { @@ -42,7 +43,7 @@ namespace osu.Game.Rulesets.Objects.Legacy.Taiko }; } - protected override HitObject CreateHold(Vector2 position, bool newCombo, double endTime) + protected override HitObject CreateHold(Vector2 position, bool newCombo, int comboOffset, double endTime) { return null; } diff --git a/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertSlider.cs b/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertSlider.cs index 8a9a0db0a7..11c0a2baae 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertSlider.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertSlider.cs @@ -1,17 +1,13 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using osu.Game.Rulesets.Objects.Types; - namespace osu.Game.Rulesets.Objects.Legacy.Taiko { /// /// Legacy osu!taiko Slider-type, used for parsing Beatmaps. /// - internal sealed class ConvertSlider : Legacy.ConvertSlider, IHasCombo + internal sealed class ConvertSlider : Legacy.ConvertSlider { - public bool NewCombo { get; set; } - protected override HitWindows CreateHitWindows() => null; } } diff --git a/osu.Game/Rulesets/Objects/Types/IHasCombo.cs b/osu.Game/Rulesets/Objects/Types/IHasCombo.cs index cb8b6f495a..95f1a1cb3d 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; } } }