From 6a6ccbc09fce5e23857afaf5feabaa77673dec65 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 19 Jun 2024 07:31:53 +0200 Subject: [PATCH 01/11] Make list of breaks bindable --- .../Mods/TestSceneCatchModNoScope.cs | 2 +- .../Mods/TestSceneOsuModAlternate.cs | 2 +- .../Mods/TestSceneOsuModNoScope.cs | 2 +- .../Mods/TestSceneOsuModSingleTap.cs | 2 +- .../Mods/TestSceneTaikoModSingleTap.cs | 2 +- osu.Game.Tests/Editing/Checks/CheckBreaksTest.cs | 15 +++++++-------- .../Editing/Checks/CheckDrainLengthTest.cs | 2 +- osu.Game/Beatmaps/Beatmap.cs | 3 ++- osu.Game/Beatmaps/IBeatmap.cs | 3 ++- .../Rulesets/Difficulty/DifficultyCalculator.cs | 3 ++- .../Components/Timeline/TimelineBreakDisplay.cs | 5 +++-- osu.Game/Screens/Edit/EditorBeatmap.cs | 2 +- 12 files changed, 23 insertions(+), 20 deletions(-) diff --git a/osu.Game.Rulesets.Catch.Tests/Mods/TestSceneCatchModNoScope.cs b/osu.Game.Rulesets.Catch.Tests/Mods/TestSceneCatchModNoScope.cs index c48bf7adc9..c8f7da1aae 100644 --- a/osu.Game.Rulesets.Catch.Tests/Mods/TestSceneCatchModNoScope.cs +++ b/osu.Game.Rulesets.Catch.Tests/Mods/TestSceneCatchModNoScope.cs @@ -74,7 +74,7 @@ namespace osu.Game.Rulesets.Catch.Tests.Mods StartTime = 5000, } }, - Breaks = new List + Breaks = { new BreakPeriod(2000, 4000), } diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModAlternate.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModAlternate.cs index 88c81c7a39..7375617aa8 100644 --- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModAlternate.cs +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModAlternate.cs @@ -133,7 +133,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods Autoplay = false, Beatmap = new Beatmap { - Breaks = new List + Breaks = { new BreakPeriod(500, 2000), }, diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModNoScope.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModNoScope.cs index 9dfa76fc8e..d3996ebc3b 100644 --- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModNoScope.cs +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModNoScope.cs @@ -46,7 +46,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods StartTime = 5000, } }, - Breaks = new List + Breaks = { new BreakPeriod(2000, 4000), } diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModSingleTap.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModSingleTap.cs index 402c680b46..bd2b205ac8 100644 --- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModSingleTap.cs +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModSingleTap.cs @@ -132,7 +132,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods Autoplay = false, Beatmap = new Beatmap { - Breaks = new List + Breaks = { new BreakPeriod(500, 2000), }, diff --git a/osu.Game.Rulesets.Taiko.Tests/Mods/TestSceneTaikoModSingleTap.cs b/osu.Game.Rulesets.Taiko.Tests/Mods/TestSceneTaikoModSingleTap.cs index 0cd3b85f8e..3a11a91f82 100644 --- a/osu.Game.Rulesets.Taiko.Tests/Mods/TestSceneTaikoModSingleTap.cs +++ b/osu.Game.Rulesets.Taiko.Tests/Mods/TestSceneTaikoModSingleTap.cs @@ -177,7 +177,7 @@ namespace osu.Game.Rulesets.Taiko.Tests.Mods Autoplay = false, Beatmap = new Beatmap { - Breaks = new List + Breaks = { new BreakPeriod(100, 1600), }, diff --git a/osu.Game.Tests/Editing/Checks/CheckBreaksTest.cs b/osu.Game.Tests/Editing/Checks/CheckBreaksTest.cs index 28556566ba..f53dd9a62a 100644 --- a/osu.Game.Tests/Editing/Checks/CheckBreaksTest.cs +++ b/osu.Game.Tests/Editing/Checks/CheckBreaksTest.cs @@ -1,7 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System.Collections.Generic; using System.Linq; using NUnit.Framework; using osu.Game.Beatmaps; @@ -29,7 +28,7 @@ namespace osu.Game.Tests.Editing.Checks { var beatmap = new Beatmap { - Breaks = new List + Breaks = { new BreakPeriod(0, 649) } @@ -52,7 +51,7 @@ namespace osu.Game.Tests.Editing.Checks new HitCircle { StartTime = 0 }, new HitCircle { StartTime = 1_200 } }, - Breaks = new List + Breaks = { new BreakPeriod(100, 751) } @@ -75,7 +74,7 @@ namespace osu.Game.Tests.Editing.Checks new HitCircle { StartTime = 0 }, new HitCircle { StartTime = 1_298 } }, - Breaks = new List + Breaks = { new BreakPeriod(200, 850) } @@ -98,7 +97,7 @@ namespace osu.Game.Tests.Editing.Checks new HitCircle { StartTime = 0 }, new HitCircle { StartTime = 1200 } }, - Breaks = new List + Breaks = { new BreakPeriod(1398, 2300) } @@ -121,7 +120,7 @@ namespace osu.Game.Tests.Editing.Checks new HitCircle { StartTime = 1100 }, new HitCircle { StartTime = 1500 } }, - Breaks = new List + Breaks = { new BreakPeriod(0, 652) } @@ -145,7 +144,7 @@ namespace osu.Game.Tests.Editing.Checks new HitCircle { StartTime = 1_297 }, new HitCircle { StartTime = 1_298 } }, - Breaks = new List + Breaks = { new BreakPeriod(200, 850) } @@ -168,7 +167,7 @@ namespace osu.Game.Tests.Editing.Checks new HitCircle { StartTime = 0 }, new HitCircle { StartTime = 1_300 } }, - Breaks = new List + Breaks = { new BreakPeriod(200, 850) } diff --git a/osu.Game.Tests/Editing/Checks/CheckDrainLengthTest.cs b/osu.Game.Tests/Editing/Checks/CheckDrainLengthTest.cs index 1b5c5c398f..be9aa711cb 100644 --- a/osu.Game.Tests/Editing/Checks/CheckDrainLengthTest.cs +++ b/osu.Game.Tests/Editing/Checks/CheckDrainLengthTest.cs @@ -53,7 +53,7 @@ namespace osu.Game.Tests.Editing.Checks new HitCircle { StartTime = 0 }, new HitCircle { StartTime = 40_000 } }, - Breaks = new List + Breaks = { new BreakPeriod(10_000, 21_000) } diff --git a/osu.Game/Beatmaps/Beatmap.cs b/osu.Game/Beatmaps/Beatmap.cs index ae77e4adcf..510410bc09 100644 --- a/osu.Game/Beatmaps/Beatmap.cs +++ b/osu.Game/Beatmaps/Beatmap.cs @@ -8,6 +8,7 @@ using System.Collections.Generic; using System.Linq; using osu.Game.Beatmaps.ControlPoints; using Newtonsoft.Json; +using osu.Framework.Bindables; using osu.Game.IO.Serialization.Converters; namespace osu.Game.Beatmaps @@ -61,7 +62,7 @@ namespace osu.Game.Beatmaps public ControlPointInfo ControlPointInfo { get; set; } = new ControlPointInfo(); - public List Breaks { get; set; } = new List(); + public BindableList Breaks { get; set; } = new BindableList(); public List UnhandledEventLines { get; set; } = new List(); diff --git a/osu.Game/Beatmaps/IBeatmap.cs b/osu.Game/Beatmaps/IBeatmap.cs index 5cc38e5b84..072e246a36 100644 --- a/osu.Game/Beatmaps/IBeatmap.cs +++ b/osu.Game/Beatmaps/IBeatmap.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Linq; +using osu.Framework.Bindables; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Beatmaps.Timing; using osu.Game.Rulesets.Objects; @@ -40,7 +41,7 @@ namespace osu.Game.Beatmaps /// /// The breaks in this beatmap. /// - List Breaks { get; } + BindableList Breaks { get; } /// /// All lines from the [Events] section which aren't handled in the encoding process yet. diff --git a/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs b/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs index d37cfc28b9..97e8e15975 100644 --- a/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs +++ b/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs @@ -9,6 +9,7 @@ using System.Linq; using System.Threading; using JetBrains.Annotations; using osu.Framework.Audio.Track; +using osu.Framework.Bindables; using osu.Framework.Extensions.IEnumerableExtensions; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; @@ -329,7 +330,7 @@ namespace osu.Game.Rulesets.Difficulty set => baseBeatmap.Difficulty = value; } - public List Breaks => baseBeatmap.Breaks; + public BindableList Breaks => baseBeatmap.Breaks; public List UnhandledEventLines => baseBeatmap.UnhandledEventLines; public double TotalBreakTime => baseBeatmap.TotalBreakTime; diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBreakDisplay.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBreakDisplay.cs index 587db23e9a..5fdfda25e5 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBreakDisplay.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBreakDisplay.cs @@ -27,8 +27,9 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline { base.LoadBeatmap(beatmap); - // TODO: this will have to be mutable soon enough - breaks.AddRange(beatmap.Breaks); + breaks.UnbindAll(); + breaks.BindTo(beatmap.Breaks); + breaks.BindCollectionChanged((_, _) => breakCache.Invalidate()); } protected override void Update() diff --git a/osu.Game/Screens/Edit/EditorBeatmap.cs b/osu.Game/Screens/Edit/EditorBeatmap.cs index 5be1d27805..f4be987547 100644 --- a/osu.Game/Screens/Edit/EditorBeatmap.cs +++ b/osu.Game/Screens/Edit/EditorBeatmap.cs @@ -172,7 +172,7 @@ namespace osu.Game.Screens.Edit set => PlayableBeatmap.ControlPointInfo = value; } - public List Breaks => PlayableBeatmap.Breaks; + public BindableList Breaks => PlayableBeatmap.Breaks; public List UnhandledEventLines => PlayableBeatmap.UnhandledEventLines; From 1f692f5fc7f450aa7336a235ee60affcaa0d2fb4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 19 Jun 2024 09:01:33 +0200 Subject: [PATCH 02/11] Make `BreakPeriod` a struct --- osu.Game/Beatmaps/Timing/BreakPeriod.cs | 11 +++-- .../Components/Timeline/TimelineBreak.cs | 48 +++++++++++-------- .../Timeline/TimelineBreakDisplay.cs | 29 +++-------- 3 files changed, 43 insertions(+), 45 deletions(-) diff --git a/osu.Game/Beatmaps/Timing/BreakPeriod.cs b/osu.Game/Beatmaps/Timing/BreakPeriod.cs index 4c90b16745..f16a3c27a1 100644 --- a/osu.Game/Beatmaps/Timing/BreakPeriod.cs +++ b/osu.Game/Beatmaps/Timing/BreakPeriod.cs @@ -1,11 +1,12 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using osu.Game.Screens.Play; namespace osu.Game.Beatmaps.Timing { - public class BreakPeriod + public readonly struct BreakPeriod : IEquatable { /// /// The minimum duration required for a break to have any effect. @@ -15,12 +16,12 @@ namespace osu.Game.Beatmaps.Timing /// /// The break start time. /// - public double StartTime; + public double StartTime { get; init; } /// /// The break end time. /// - public double EndTime; + public double EndTime { get; init; } /// /// The break duration. @@ -49,5 +50,9 @@ namespace osu.Game.Beatmaps.Timing /// The time to check in milliseconds. /// Whether the time falls within this . public bool Contains(double time) => time >= StartTime && time <= EndTime - BreakOverlay.BREAK_FADE_DURATION; + + public bool Equals(BreakPeriod other) => StartTime.Equals(other.StartTime) && EndTime.Equals(other.EndTime); + public override bool Equals(object? obj) => obj is BreakPeriod other && Equals(other); + public override int GetHashCode() => HashCode.Combine(StartTime, EndTime); } } diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBreak.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBreak.cs index 785eba2042..cec4b9b659 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBreak.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBreak.cs @@ -5,6 +5,7 @@ using System; using System.Diagnostics; using System.Linq; using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -20,11 +21,11 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline { public partial class TimelineBreak : CompositeDrawable { - public BreakPeriod Break { get; } + public Bindable Break { get; } = new Bindable(); public TimelineBreak(BreakPeriod b) { - Break = b; + Break.Value = b; } [BackgroundDependencyLoader] @@ -48,40 +49,46 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline Alpha = 0.4f, }, }, - new DragHandle(Break, isStartHandle: true) + new DragHandle(isStartHandle: true) { + Break = { BindTarget = Break }, Anchor = Anchor.TopLeft, Origin = Anchor.TopLeft, - Action = (time, breakPeriod) => breakPeriod.StartTime = time, + Action = (time, breakPeriod) => breakPeriod with { StartTime = time }, }, - new DragHandle(Break, isStartHandle: false) + new DragHandle(isStartHandle: false) { + Break = { BindTarget = Break }, Anchor = Anchor.TopRight, Origin = Anchor.TopRight, - Action = (time, breakPeriod) => breakPeriod.EndTime = time, + Action = (time, breakPeriod) => breakPeriod with { EndTime = time }, }, }; } - protected override void Update() + protected override void LoadComplete() { - base.Update(); + base.LoadComplete(); - X = (float)Break.StartTime; - Width = (float)Break.Duration; + Break.BindValueChanged(_ => + { + X = (float)Break.Value.StartTime; + Width = (float)Break.Value.Duration; + }, true); } private partial class DragHandle : FillFlowContainer { + public Bindable Break { get; } = new Bindable(); + public new Anchor Anchor { get => base.Anchor; init => base.Anchor = value; } - public Action? Action { get; init; } + public Func? Action { get; init; } - private readonly BreakPeriod breakPeriod; private readonly bool isStartHandle; private Container handle = null!; @@ -99,9 +106,8 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline [Resolved] private OsuColour colours { get; set; } = null!; - public DragHandle(BreakPeriod breakPeriod, bool isStartHandle) + public DragHandle(bool isStartHandle) { - this.breakPeriod = breakPeriod; this.isStartHandle = isStartHandle; } @@ -164,13 +170,13 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline changeHandler?.BeginChange(); updateState(); - double min = beatmap.HitObjects.Last(ho => ho.GetEndTime() <= breakPeriod.StartTime).GetEndTime(); - double max = beatmap.HitObjects.First(ho => ho.StartTime >= breakPeriod.EndTime).StartTime; + double min = beatmap.HitObjects.Last(ho => ho.GetEndTime() <= Break.Value.StartTime).GetEndTime(); + double max = beatmap.HitObjects.First(ho => ho.StartTime >= Break.Value.EndTime).StartTime; if (isStartHandle) - max = Math.Min(max, breakPeriod.EndTime - BreakPeriod.MIN_BREAK_DURATION); + max = Math.Min(max, Break.Value.EndTime - BreakPeriod.MIN_BREAK_DURATION); else - min = Math.Max(min, breakPeriod.StartTime + BreakPeriod.MIN_BREAK_DURATION); + min = Math.Max(min, Break.Value.StartTime + BreakPeriod.MIN_BREAK_DURATION); allowedDragRange = (min, max); @@ -183,11 +189,13 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline Debug.Assert(allowedDragRange != null); - if (timeline.FindSnappedPositionAndTime(e.ScreenSpaceMousePosition).Time is double time + if (Action != null + && timeline.FindSnappedPositionAndTime(e.ScreenSpaceMousePosition).Time is double time && time > allowedDragRange.Value.min && time < allowedDragRange.Value.max) { - Action?.Invoke(time, breakPeriod); + int index = beatmap.Breaks.IndexOf(Break.Value); + beatmap.Breaks[index] = Break.Value = Action.Invoke(time, Break.Value); } updateState(); diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBreakDisplay.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBreakDisplay.cs index 5fdfda25e5..eaa31aea1e 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBreakDisplay.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBreakDisplay.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Collections.Specialized; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Caching; @@ -29,7 +30,11 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline breaks.UnbindAll(); breaks.BindTo(beatmap.Breaks); - breaks.BindCollectionChanged((_, _) => breakCache.Invalidate()); + breaks.BindCollectionChanged((_, e) => + { + if (e.Action != NotifyCollectionChangedAction.Replace) + breakCache.Invalidate(); + }); } protected override void Update() @@ -57,14 +62,8 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline private void recreateBreaks() { - // Remove groups outside the visible range - foreach (TimelineBreak drawableBreak in this) - { - if (!shouldBeVisible(drawableBreak.Break)) - drawableBreak.Expire(); - } + Clear(); - // Add remaining ones for (int i = 0; i < breaks.Count; i++) { var breakPeriod = breaks[i]; @@ -72,20 +71,6 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline if (!shouldBeVisible(breakPeriod)) continue; - bool alreadyVisible = false; - - foreach (var b in this) - { - if (ReferenceEquals(b.Break, breakPeriod)) - { - alreadyVisible = true; - break; - } - } - - if (alreadyVisible) - continue; - Add(new TimelineBreak(breakPeriod)); } } From 4022a8b06cc056b210047f55bff4b3a59d7c24e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 19 Jun 2024 10:11:04 +0200 Subject: [PATCH 03/11] Implement automatic break period generation --- osu.Game/Beatmaps/Timing/BreakPeriod.cs | 32 ++++++++-- osu.Game/Rulesets/Edit/Checks/CheckBreaks.cs | 14 ++-- .../Components/Timeline/TimelineBreak.cs | 4 +- osu.Game/Screens/Edit/EditorBeatmap.cs | 2 +- .../Screens/Edit/EditorBeatmapProcessor.cs | 64 +++++++++++++++++++ osu.Game/Screens/Edit/ManualBreakPeriod.cs | 15 +++++ 6 files changed, 113 insertions(+), 18 deletions(-) create mode 100644 osu.Game/Screens/Edit/EditorBeatmapProcessor.cs create mode 100644 osu.Game/Screens/Edit/ManualBreakPeriod.cs diff --git a/osu.Game/Beatmaps/Timing/BreakPeriod.cs b/osu.Game/Beatmaps/Timing/BreakPeriod.cs index f16a3c27a1..89f0fd6a55 100644 --- a/osu.Game/Beatmaps/Timing/BreakPeriod.cs +++ b/osu.Game/Beatmaps/Timing/BreakPeriod.cs @@ -6,22 +6,39 @@ using osu.Game.Screens.Play; namespace osu.Game.Beatmaps.Timing { - public readonly struct BreakPeriod : IEquatable + public record BreakPeriod { + /// + /// The minimum gap between the start of the break and the previous object. + /// + public const double GAP_BEFORE_BREAK = 200; + + /// + /// The minimum gap between the end of the break and the next object. + /// Based on osu! preempt time at AR=10. + /// See also: https://github.com/ppy/osu/issues/14330#issuecomment-1002158551 + /// + public const double GAP_AFTER_BREAK = 450; + /// /// The minimum duration required for a break to have any effect. /// public const double MIN_BREAK_DURATION = 650; + /// + /// The minimum required duration of a gap between two objects such that a break can be placed between them. + /// + public const double MIN_GAP_DURATION = GAP_BEFORE_BREAK + MIN_BREAK_DURATION + GAP_AFTER_BREAK; + /// /// The break start time. /// - public double StartTime { get; init; } + public double StartTime { get; } /// /// The break end time. /// - public double EndTime { get; init; } + public double EndTime { get; } /// /// The break duration. @@ -51,8 +68,13 @@ namespace osu.Game.Beatmaps.Timing /// Whether the time falls within this . public bool Contains(double time) => time >= StartTime && time <= EndTime - BreakOverlay.BREAK_FADE_DURATION; - public bool Equals(BreakPeriod other) => StartTime.Equals(other.StartTime) && EndTime.Equals(other.EndTime); - public override bool Equals(object? obj) => obj is BreakPeriod other && Equals(other); + public bool Intersects(BreakPeriod other) => StartTime <= other.EndTime && EndTime >= other.StartTime; + + public virtual bool Equals(BreakPeriod? other) => + other != null + && StartTime == other.StartTime + && EndTime == other.EndTime; + public override int GetHashCode() => HashCode.Combine(StartTime, EndTime); } } diff --git a/osu.Game/Rulesets/Edit/Checks/CheckBreaks.cs b/osu.Game/Rulesets/Edit/Checks/CheckBreaks.cs index 0842ff5453..f7be36beab 100644 --- a/osu.Game/Rulesets/Edit/Checks/CheckBreaks.cs +++ b/osu.Game/Rulesets/Edit/Checks/CheckBreaks.cs @@ -13,13 +13,7 @@ namespace osu.Game.Rulesets.Edit.Checks { // Breaks may be off by 1 ms. private const int leniency_threshold = 1; - private const double minimum_gap_before_break = 200; - // Break end time depends on the upcoming object's pre-empt time. - // As things stand, "pre-empt time" is only defined for osu! standard - // This is a generic value representing AR=10 - // Relevant: https://github.com/ppy/osu/issues/14330#issuecomment-1002158551 - private const double min_end_threshold = 450; public CheckMetadata Metadata => new CheckMetadata(CheckCategory.Events, "Breaks not achievable using the editor"); public IEnumerable PossibleTemplates => new IssueTemplate[] @@ -45,8 +39,8 @@ namespace osu.Game.Rulesets.Edit.Checks if (previousObjectEndTimeIndex >= 0) { double gapBeforeBreak = breakPeriod.StartTime - endTimes[previousObjectEndTimeIndex]; - if (gapBeforeBreak < minimum_gap_before_break - leniency_threshold) - yield return new IssueTemplateEarlyStart(this).Create(breakPeriod.StartTime, minimum_gap_before_break - gapBeforeBreak); + if (gapBeforeBreak < BreakPeriod.GAP_BEFORE_BREAK - leniency_threshold) + yield return new IssueTemplateEarlyStart(this).Create(breakPeriod.StartTime, BreakPeriod.GAP_BEFORE_BREAK - gapBeforeBreak); } int nextObjectStartTimeIndex = startTimes.BinarySearch(breakPeriod.EndTime); @@ -55,8 +49,8 @@ namespace osu.Game.Rulesets.Edit.Checks if (nextObjectStartTimeIndex < startTimes.Count) { double gapAfterBreak = startTimes[nextObjectStartTimeIndex] - breakPeriod.EndTime; - if (gapAfterBreak < min_end_threshold - leniency_threshold) - yield return new IssueTemplateLateEnd(this).Create(breakPeriod.StartTime, min_end_threshold - gapAfterBreak); + if (gapAfterBreak < BreakPeriod.GAP_AFTER_BREAK - leniency_threshold) + yield return new IssueTemplateLateEnd(this).Create(breakPeriod.StartTime, BreakPeriod.GAP_AFTER_BREAK - gapAfterBreak); } } } diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBreak.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBreak.cs index cec4b9b659..b9651ccd81 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBreak.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBreak.cs @@ -54,14 +54,14 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline Break = { BindTarget = Break }, Anchor = Anchor.TopLeft, Origin = Anchor.TopLeft, - Action = (time, breakPeriod) => breakPeriod with { StartTime = time }, + Action = (time, breakPeriod) => new ManualBreakPeriod(time, breakPeriod.EndTime), }, new DragHandle(isStartHandle: false) { Break = { BindTarget = Break }, Anchor = Anchor.TopRight, Origin = Anchor.TopRight, - Action = (time, breakPeriod) => breakPeriod with { EndTime = time }, + Action = (time, breakPeriod) => new ManualBreakPeriod(breakPeriod.StartTime, time), }, }; } diff --git a/osu.Game/Screens/Edit/EditorBeatmap.cs b/osu.Game/Screens/Edit/EditorBeatmap.cs index f4be987547..80586a923d 100644 --- a/osu.Game/Screens/Edit/EditorBeatmap.cs +++ b/osu.Game/Screens/Edit/EditorBeatmap.cs @@ -105,7 +105,7 @@ namespace osu.Game.Screens.Edit BeatmapSkin.BeatmapSkinChanged += SaveState; } - beatmapProcessor = playableBeatmap.BeatmapInfo.Ruleset.CreateInstance().CreateBeatmapProcessor(this); + beatmapProcessor = new EditorBeatmapProcessor(this, playableBeatmap.BeatmapInfo.Ruleset.CreateInstance()); foreach (var obj in HitObjects) trackStartTime(obj); diff --git a/osu.Game/Screens/Edit/EditorBeatmapProcessor.cs b/osu.Game/Screens/Edit/EditorBeatmapProcessor.cs new file mode 100644 index 0000000000..5b1cf281bb --- /dev/null +++ b/osu.Game/Screens/Edit/EditorBeatmapProcessor.cs @@ -0,0 +1,64 @@ +// 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.Linq; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.Timing; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Objects; + +namespace osu.Game.Screens.Edit +{ + public class EditorBeatmapProcessor : IBeatmapProcessor + { + public IBeatmap Beatmap { get; } + + private readonly IBeatmapProcessor? rulesetBeatmapProcessor; + + public EditorBeatmapProcessor(IBeatmap beatmap, Ruleset ruleset) + { + Beatmap = beatmap; + rulesetBeatmapProcessor = ruleset.CreateBeatmapProcessor(beatmap); + } + + public void PreProcess() + { + rulesetBeatmapProcessor?.PreProcess(); + } + + public void PostProcess() + { + rulesetBeatmapProcessor?.PostProcess(); + + autoGenerateBreaks(); + } + + private void autoGenerateBreaks() + { + Beatmap.Breaks.RemoveAll(b => b is not ManualBreakPeriod); + + for (int i = 1; i < Beatmap.HitObjects.Count; ++i) + { + double previousObjectEndTime = Beatmap.HitObjects[i - 1].GetEndTime(); + double nextObjectStartTime = Beatmap.HitObjects[i].StartTime; + + if (nextObjectStartTime - previousObjectEndTime < BreakPeriod.MIN_GAP_DURATION) + continue; + + double breakStartTime = previousObjectEndTime + BreakPeriod.GAP_BEFORE_BREAK; + double breakEndTime = nextObjectStartTime - Math.Max(BreakPeriod.GAP_AFTER_BREAK, Beatmap.ControlPointInfo.TimingPointAt(nextObjectStartTime).BeatLength * 2); + + if (breakEndTime - breakStartTime < BreakPeriod.MIN_BREAK_DURATION) + continue; + + var breakPeriod = new BreakPeriod(breakStartTime, breakEndTime); + + if (Beatmap.Breaks.Any(b => b.Intersects(breakPeriod))) + continue; + + Beatmap.Breaks.Add(breakPeriod); + } + } + } +} diff --git a/osu.Game/Screens/Edit/ManualBreakPeriod.cs b/osu.Game/Screens/Edit/ManualBreakPeriod.cs new file mode 100644 index 0000000000..719784b500 --- /dev/null +++ b/osu.Game/Screens/Edit/ManualBreakPeriod.cs @@ -0,0 +1,15 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Game.Beatmaps.Timing; + +namespace osu.Game.Screens.Edit +{ + public record ManualBreakPeriod : BreakPeriod + { + public ManualBreakPeriod(double startTime, double endTime) + : base(startTime, endTime) + { + } + } +} From 58701b17f842b64c6824ed3c8806cef6565905ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 19 Jun 2024 10:22:14 +0200 Subject: [PATCH 04/11] Add patcher support for breaks --- .../Edit/LegacyEditorBeatmapPatcher.cs | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/osu.Game/Screens/Edit/LegacyEditorBeatmapPatcher.cs b/osu.Game/Screens/Edit/LegacyEditorBeatmapPatcher.cs index bb9f702cb5..a1ee41fc48 100644 --- a/osu.Game/Screens/Edit/LegacyEditorBeatmapPatcher.cs +++ b/osu.Game/Screens/Edit/LegacyEditorBeatmapPatcher.cs @@ -45,6 +45,7 @@ namespace osu.Game.Screens.Edit editorBeatmap.BeginChange(); processHitObjects(result, () => newBeatmap ??= readBeatmap(newState)); processTimingPoints(() => newBeatmap ??= readBeatmap(newState)); + processBreaks(() => newBeatmap ??= readBeatmap(newState)); processHitObjectLocalData(() => newBeatmap ??= readBeatmap(newState)); editorBeatmap.EndChange(); } @@ -75,6 +76,27 @@ namespace osu.Game.Screens.Edit } } + private void processBreaks(Func getNewBeatmap) + { + var newBreaks = getNewBeatmap().Breaks.ToArray(); + + foreach (var oldBreak in editorBeatmap.Breaks.ToArray()) + { + if (newBreaks.Any(b => b.Equals(oldBreak))) + continue; + + editorBeatmap.Breaks.Remove(oldBreak); + } + + foreach (var newBreak in newBreaks) + { + if (editorBeatmap.Breaks.Any(b => b.Equals(newBreak))) + continue; + + editorBeatmap.Breaks.Add(newBreak); + } + } + private void processHitObjects(DiffResult result, Func getNewBeatmap) { findChangedIndices(result, LegacyDecoder.Section.HitObjects, out var removedIndices, out var addedIndices); From 7ed587b783ce5c69de2eceb36f37f36cbd8dae9c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 19 Jun 2024 10:26:01 +0200 Subject: [PATCH 05/11] Fix summary timeline not reloading properly on break addition/removal --- .../Timelines/Summary/Parts/BreakPart.cs | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/BreakPart.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/BreakPart.cs index 41ecb44d9d..1ba552d646 100644 --- a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/BreakPart.cs +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/BreakPart.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Shapes; using osu.Game.Beatmaps.Timing; @@ -14,11 +15,19 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts /// public partial class BreakPart : TimelinePart { + private readonly BindableList breaks = new BindableList(); + protected override void LoadBeatmap(EditorBeatmap beatmap) { base.LoadBeatmap(beatmap); - foreach (var breakPeriod in beatmap.Breaks) - Add(new BreakVisualisation(breakPeriod)); + + breaks.UnbindAll(); + breaks.BindTo(beatmap.Breaks); + breaks.BindCollectionChanged((_, _) => + { + foreach (var breakPeriod in beatmap.Breaks) + Add(new BreakVisualisation(breakPeriod)); + }, true); } private partial class BreakVisualisation : Circle @@ -31,12 +40,6 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts RelativePositionAxes = Axes.X; RelativeSizeAxes = Axes.Both; - } - - protected override void Update() - { - base.Update(); - X = (float)breakPeriod.StartTime; Width = (float)breakPeriod.Duration; } From 7311a7ffd711c46fd10b693c2ef960697fa8f8b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 19 Jun 2024 10:51:37 +0200 Subject: [PATCH 06/11] Purge manual breaks if they intersect with an actual hitobject --- osu.Game/Screens/Edit/EditorBeatmapProcessor.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/osu.Game/Screens/Edit/EditorBeatmapProcessor.cs b/osu.Game/Screens/Edit/EditorBeatmapProcessor.cs index 5b1cf281bb..37bf915cec 100644 --- a/osu.Game/Screens/Edit/EditorBeatmapProcessor.cs +++ b/osu.Game/Screens/Edit/EditorBeatmapProcessor.cs @@ -38,6 +38,12 @@ namespace osu.Game.Screens.Edit { Beatmap.Breaks.RemoveAll(b => b is not ManualBreakPeriod); + foreach (var manualBreak in Beatmap.Breaks.ToList()) + { + if (Beatmap.HitObjects.Any(ho => ho.StartTime <= manualBreak.EndTime && ho.GetEndTime() >= manualBreak.StartTime)) + Beatmap.Breaks.Remove(manualBreak); + } + for (int i = 1; i < Beatmap.HitObjects.Count; ++i) { double previousObjectEndTime = Beatmap.HitObjects[i - 1].GetEndTime(); From 439079876157020272063e39d957590b0d5a6651 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 19 Jun 2024 11:14:38 +0200 Subject: [PATCH 07/11] Add test coverage for automatic break generation --- .../TestSceneEditorBeatmapProcessor.cs | 300 ++++++++++++++++++ 1 file changed, 300 insertions(+) create mode 100644 osu.Game.Tests/Editing/TestSceneEditorBeatmapProcessor.cs diff --git a/osu.Game.Tests/Editing/TestSceneEditorBeatmapProcessor.cs b/osu.Game.Tests/Editing/TestSceneEditorBeatmapProcessor.cs new file mode 100644 index 0000000000..02ce3815ec --- /dev/null +++ b/osu.Game.Tests/Editing/TestSceneEditorBeatmapProcessor.cs @@ -0,0 +1,300 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using NUnit.Framework; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Beatmaps.Timing; +using osu.Game.Rulesets.Osu; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Screens.Edit; + +namespace osu.Game.Tests.Editing +{ + [TestFixture] + public class TestSceneEditorBeatmapProcessor + { + [Test] + public void TestEmptyBeatmap() + { + var controlPoints = new ControlPointInfo(); + controlPoints.Add(0, new TimingControlPoint { BeatLength = 500 }); + var beatmap = new Beatmap + { + ControlPointInfo = controlPoints, + }; + + var beatmapProcessor = new EditorBeatmapProcessor(beatmap, new OsuRuleset()); + beatmapProcessor.PreProcess(); + beatmapProcessor.PostProcess(); + + Assert.That(beatmap.Breaks, Is.Empty); + } + + [Test] + public void TestSingleObjectBeatmap() + { + var controlPoints = new ControlPointInfo(); + controlPoints.Add(0, new TimingControlPoint { BeatLength = 500 }); + var beatmap = new Beatmap + { + ControlPointInfo = controlPoints, + HitObjects = + { + new HitCircle { StartTime = 1000 }, + } + }; + + var beatmapProcessor = new EditorBeatmapProcessor(beatmap, new OsuRuleset()); + beatmapProcessor.PreProcess(); + beatmapProcessor.PostProcess(); + + Assert.That(beatmap.Breaks, Is.Empty); + } + + [Test] + public void TestTwoObjectsCloseTogether() + { + var controlPoints = new ControlPointInfo(); + controlPoints.Add(0, new TimingControlPoint { BeatLength = 500 }); + var beatmap = new Beatmap + { + ControlPointInfo = controlPoints, + HitObjects = + { + new HitCircle { StartTime = 1000 }, + new HitCircle { StartTime = 2000 }, + } + }; + + var beatmapProcessor = new EditorBeatmapProcessor(beatmap, new OsuRuleset()); + beatmapProcessor.PreProcess(); + beatmapProcessor.PostProcess(); + + Assert.That(beatmap.Breaks, Is.Empty); + } + + [Test] + public void TestTwoObjectsFarApart() + { + var controlPoints = new ControlPointInfo(); + controlPoints.Add(0, new TimingControlPoint { BeatLength = 500 }); + var beatmap = new Beatmap + { + ControlPointInfo = controlPoints, + HitObjects = + { + new HitCircle { StartTime = 1000 }, + new HitCircle { StartTime = 5000 }, + } + }; + + var beatmapProcessor = new EditorBeatmapProcessor(beatmap, new OsuRuleset()); + beatmapProcessor.PreProcess(); + beatmapProcessor.PostProcess(); + + Assert.Multiple(() => + { + Assert.That(beatmap.Breaks, Has.Count.EqualTo(1)); + Assert.That(beatmap.Breaks[0].StartTime, Is.EqualTo(1200)); + Assert.That(beatmap.Breaks[0].EndTime, Is.EqualTo(4000)); + }); + } + + [Test] + public void TestBreaksAreFused() + { + var controlPoints = new ControlPointInfo(); + controlPoints.Add(0, new TimingControlPoint { BeatLength = 500 }); + var beatmap = new Beatmap + { + ControlPointInfo = controlPoints, + HitObjects = + { + new HitCircle { StartTime = 1000 }, + new HitCircle { StartTime = 9000 }, + }, + Breaks = + { + new BreakPeriod(1200, 4000), + new BreakPeriod(5200, 8000), + } + }; + + var beatmapProcessor = new EditorBeatmapProcessor(beatmap, new OsuRuleset()); + beatmapProcessor.PreProcess(); + beatmapProcessor.PostProcess(); + + Assert.Multiple(() => + { + Assert.That(beatmap.Breaks, Has.Count.EqualTo(1)); + Assert.That(beatmap.Breaks[0].StartTime, Is.EqualTo(1200)); + Assert.That(beatmap.Breaks[0].EndTime, Is.EqualTo(8000)); + }); + } + + [Test] + public void TestBreaksAreSplit() + { + var controlPoints = new ControlPointInfo(); + controlPoints.Add(0, new TimingControlPoint { BeatLength = 500 }); + var beatmap = new Beatmap + { + ControlPointInfo = controlPoints, + HitObjects = + { + new HitCircle { StartTime = 1000 }, + new HitCircle { StartTime = 5000 }, + new HitCircle { StartTime = 9000 }, + }, + Breaks = + { + new BreakPeriod(1200, 8000), + } + }; + + var beatmapProcessor = new EditorBeatmapProcessor(beatmap, new OsuRuleset()); + beatmapProcessor.PreProcess(); + beatmapProcessor.PostProcess(); + + Assert.Multiple(() => + { + Assert.That(beatmap.Breaks, Has.Count.EqualTo(2)); + Assert.That(beatmap.Breaks[0].StartTime, Is.EqualTo(1200)); + Assert.That(beatmap.Breaks[0].EndTime, Is.EqualTo(4000)); + Assert.That(beatmap.Breaks[1].StartTime, Is.EqualTo(5200)); + Assert.That(beatmap.Breaks[1].EndTime, Is.EqualTo(8000)); + }); + } + + [Test] + public void TestBreaksAreNudged() + { + var controlPoints = new ControlPointInfo(); + controlPoints.Add(0, new TimingControlPoint { BeatLength = 500 }); + var beatmap = new Beatmap + { + ControlPointInfo = controlPoints, + HitObjects = + { + new HitCircle { StartTime = 1100 }, + new HitCircle { StartTime = 9000 }, + }, + Breaks = + { + new BreakPeriod(1200, 8000), + } + }; + + var beatmapProcessor = new EditorBeatmapProcessor(beatmap, new OsuRuleset()); + beatmapProcessor.PreProcess(); + beatmapProcessor.PostProcess(); + + Assert.Multiple(() => + { + Assert.That(beatmap.Breaks, Has.Count.EqualTo(1)); + Assert.That(beatmap.Breaks[0].StartTime, Is.EqualTo(1300)); + Assert.That(beatmap.Breaks[0].EndTime, Is.EqualTo(8000)); + }); + } + + [Test] + public void TestManualBreaksAreNotFused() + { + var controlPoints = new ControlPointInfo(); + controlPoints.Add(0, new TimingControlPoint { BeatLength = 500 }); + var beatmap = new Beatmap + { + ControlPointInfo = controlPoints, + HitObjects = + { + new HitCircle { StartTime = 1000 }, + new HitCircle { StartTime = 9000 }, + }, + Breaks = + { + new ManualBreakPeriod(1200, 4000), + new ManualBreakPeriod(5200, 8000), + } + }; + + var beatmapProcessor = new EditorBeatmapProcessor(beatmap, new OsuRuleset()); + beatmapProcessor.PreProcess(); + beatmapProcessor.PostProcess(); + + Assert.Multiple(() => + { + Assert.That(beatmap.Breaks, Has.Count.EqualTo(2)); + Assert.That(beatmap.Breaks[0].StartTime, Is.EqualTo(1200)); + Assert.That(beatmap.Breaks[0].EndTime, Is.EqualTo(4000)); + Assert.That(beatmap.Breaks[1].StartTime, Is.EqualTo(5200)); + Assert.That(beatmap.Breaks[1].EndTime, Is.EqualTo(8000)); + }); + } + + [Test] + public void TestManualBreaksAreSplit() + { + var controlPoints = new ControlPointInfo(); + controlPoints.Add(0, new TimingControlPoint { BeatLength = 500 }); + var beatmap = new Beatmap + { + ControlPointInfo = controlPoints, + HitObjects = + { + new HitCircle { StartTime = 1000 }, + new HitCircle { StartTime = 5000 }, + new HitCircle { StartTime = 9000 }, + }, + Breaks = + { + new ManualBreakPeriod(1200, 8000), + } + }; + + var beatmapProcessor = new EditorBeatmapProcessor(beatmap, new OsuRuleset()); + beatmapProcessor.PreProcess(); + beatmapProcessor.PostProcess(); + + Assert.Multiple(() => + { + Assert.That(beatmap.Breaks, Has.Count.EqualTo(2)); + Assert.That(beatmap.Breaks[0].StartTime, Is.EqualTo(1200)); + Assert.That(beatmap.Breaks[0].EndTime, Is.EqualTo(4000)); + Assert.That(beatmap.Breaks[1].StartTime, Is.EqualTo(5200)); + Assert.That(beatmap.Breaks[1].EndTime, Is.EqualTo(8000)); + }); + } + + [Test] + public void TestManualBreaksAreNotNudged() + { + var controlPoints = new ControlPointInfo(); + controlPoints.Add(0, new TimingControlPoint { BeatLength = 500 }); + var beatmap = new Beatmap + { + ControlPointInfo = controlPoints, + HitObjects = + { + new HitCircle { StartTime = 1000 }, + new HitCircle { StartTime = 9000 }, + }, + Breaks = + { + new ManualBreakPeriod(1200, 8800), + } + }; + + var beatmapProcessor = new EditorBeatmapProcessor(beatmap, new OsuRuleset()); + beatmapProcessor.PreProcess(); + beatmapProcessor.PostProcess(); + + Assert.Multiple(() => + { + Assert.That(beatmap.Breaks, Has.Count.EqualTo(1)); + Assert.That(beatmap.Breaks[0].StartTime, Is.EqualTo(1200)); + Assert.That(beatmap.Breaks[0].EndTime, Is.EqualTo(8800)); + }); + } + } +} From 2d9c3fbed24aed45f044db674b8ed6273ca9e9f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 19 Jun 2024 11:21:57 +0200 Subject: [PATCH 08/11] Remove no-longer-necessary null propagation --- osu.Game/Screens/Edit/EditorBeatmap.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Edit/EditorBeatmap.cs b/osu.Game/Screens/Edit/EditorBeatmap.cs index 80586a923d..ae0fd9130f 100644 --- a/osu.Game/Screens/Edit/EditorBeatmap.cs +++ b/osu.Game/Screens/Edit/EditorBeatmap.cs @@ -349,13 +349,13 @@ namespace osu.Game.Screens.Edit if (batchPendingUpdates.Count == 0 && batchPendingDeletes.Count == 0 && batchPendingInserts.Count == 0) return; - beatmapProcessor?.PreProcess(); + beatmapProcessor.PreProcess(); foreach (var h in batchPendingDeletes) processHitObject(h); foreach (var h in batchPendingInserts) processHitObject(h); foreach (var h in batchPendingUpdates) processHitObject(h); - beatmapProcessor?.PostProcess(); + beatmapProcessor.PostProcess(); BeatmapReprocessed?.Invoke(); From 8757e08c2c98e23566af8dd60d7486142a9c8bd5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 19 Jun 2024 11:32:08 +0200 Subject: [PATCH 09/11] Fix test failures due to automatic break generation kicking in --- osu.Game.Tests/Visual/Editing/TestSceneEditorChangeStates.cs | 4 ++++ osu.Game/Tests/Beatmaps/TestBeatmap.cs | 4 +++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorChangeStates.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorChangeStates.cs index 278b6e9626..7827347b1f 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorChangeStates.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorChangeStates.cs @@ -4,10 +4,12 @@ #nullable disable using NUnit.Framework; +using osu.Game.Beatmaps; using osu.Game.Rulesets; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Tests.Beatmaps; namespace osu.Game.Tests.Visual.Editing { @@ -15,6 +17,8 @@ namespace osu.Game.Tests.Visual.Editing { protected override Ruleset CreateEditorRuleset() => new OsuRuleset(); + protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) => new TestBeatmap(ruleset, false); + [Test] public void TestSelectedObjects() { diff --git a/osu.Game/Tests/Beatmaps/TestBeatmap.cs b/osu.Game/Tests/Beatmaps/TestBeatmap.cs index de7bcfcfaa..31ad2de62e 100644 --- a/osu.Game/Tests/Beatmaps/TestBeatmap.cs +++ b/osu.Game/Tests/Beatmaps/TestBeatmap.cs @@ -26,11 +26,13 @@ namespace osu.Game.Tests.Beatmaps BeatmapInfo = baseBeatmap.BeatmapInfo; ControlPointInfo = baseBeatmap.ControlPointInfo; - Breaks = baseBeatmap.Breaks; UnhandledEventLines = baseBeatmap.UnhandledEventLines; if (withHitObjects) + { HitObjects = baseBeatmap.HitObjects; + Breaks = baseBeatmap.Breaks; + } BeatmapInfo.Ruleset = ruleset; BeatmapInfo.Length = 75000; From 617c1341d722224862e6b0af8b375b61d290158e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 19 Jun 2024 14:53:09 +0200 Subject: [PATCH 10/11] Make `(Manual)BreakPeriod` a class again CodeFileSanity doesn't like records and it being a record wasn't doing much anymore anyway. --- osu.Game/Beatmaps/Timing/BreakPeriod.cs | 2 +- osu.Game/Screens/Edit/ManualBreakPeriod.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Beatmaps/Timing/BreakPeriod.cs b/osu.Game/Beatmaps/Timing/BreakPeriod.cs index 89f0fd6a55..d8b500227a 100644 --- a/osu.Game/Beatmaps/Timing/BreakPeriod.cs +++ b/osu.Game/Beatmaps/Timing/BreakPeriod.cs @@ -6,7 +6,7 @@ using osu.Game.Screens.Play; namespace osu.Game.Beatmaps.Timing { - public record BreakPeriod + public class BreakPeriod : IEquatable { /// /// The minimum gap between the start of the break and the previous object. diff --git a/osu.Game/Screens/Edit/ManualBreakPeriod.cs b/osu.Game/Screens/Edit/ManualBreakPeriod.cs index 719784b500..3ab77d84ce 100644 --- a/osu.Game/Screens/Edit/ManualBreakPeriod.cs +++ b/osu.Game/Screens/Edit/ManualBreakPeriod.cs @@ -5,7 +5,7 @@ using osu.Game.Beatmaps.Timing; namespace osu.Game.Screens.Edit { - public record ManualBreakPeriod : BreakPeriod + public class ManualBreakPeriod : BreakPeriod { public ManualBreakPeriod(double startTime, double endTime) : base(startTime, endTime) From a718af8af5eb8878d3237c728df66525fa72c2ac Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 19 Jun 2024 22:02:10 +0800 Subject: [PATCH 11/11] Adjust break colours to match closer to stable --- .../Edit/Components/Timelines/Summary/Parts/BreakPart.cs | 6 +----- .../Edit/Compose/Components/Timeline/TimelineBreak.cs | 6 +++--- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/BreakPart.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/BreakPart.cs index 1ba552d646..50062e8465 100644 --- a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/BreakPart.cs +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/BreakPart.cs @@ -32,12 +32,8 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts private partial class BreakVisualisation : Circle { - private readonly BreakPeriod breakPeriod; - public BreakVisualisation(BreakPeriod breakPeriod) { - this.breakPeriod = breakPeriod; - RelativePositionAxes = Axes.X; RelativeSizeAxes = Axes.Both; X = (float)breakPeriod.StartTime; @@ -45,7 +41,7 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts } [BackgroundDependencyLoader] - private void load(OsuColour colours) => Colour = colours.GreyCarmineLight; + private void load(OsuColour colours) => Colour = colours.Gray7; } } } diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBreak.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBreak.cs index b1a26bbe14..608c2bdab1 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBreak.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBreak.cs @@ -45,8 +45,8 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline Child = new Box { RelativeSizeAxes = Axes.Both, - Colour = colours.PurpleLight, - Alpha = 0.4f, + Colour = colours.Gray5, + Alpha = 0.7f, }, }, new DragHandle(isStartHandle: true) @@ -212,7 +212,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline { bool active = IsHovered || IsDragged; - var colour = colours.PurpleLighter; + var colour = colours.Gray8; if (active) colour = colour.Lighten(0.3f);