From 43ab7f49426626b3d9536af481221c98e7dca86a Mon Sep 17 00:00:00 2001 From: ratinfx Date: Sat, 4 Nov 2023 02:01:18 +0100 Subject: [PATCH 01/91] Added OpenEditorTimestamp base implementation --- osu.Game/Localisation/EditorStrings.cs | 15 +++ osu.Game/OsuGame.cs | 48 +++++++++ osu.Game/Screens/Edit/Editor.cs | 34 ++++++ .../Screens/Edit/EditorTimestampParser.cs | 101 ++++++++++++++++++ 4 files changed, 198 insertions(+) create mode 100644 osu.Game/Screens/Edit/EditorTimestampParser.cs diff --git a/osu.Game/Localisation/EditorStrings.cs b/osu.Game/Localisation/EditorStrings.cs index 93e52746c5..227dbc5e0c 100644 --- a/osu.Game/Localisation/EditorStrings.cs +++ b/osu.Game/Localisation/EditorStrings.cs @@ -119,6 +119,21 @@ namespace osu.Game.Localisation /// public static LocalisableString LimitedDistanceSnap => new TranslatableString(getKey(@"limited_distance_snap_grid"), @"Limit distance snap placement to current time"); + /// + /// "Must be in edit mode to handle editor links" + /// + public static LocalisableString MustBeInEdit => new TranslatableString(getKey(@"must_be_in_edit"), @"Must be in edit mode to handle editor links"); + + /// + /// "Failed to process timestamp" + /// + public static LocalisableString FailedToProcessTimestamp => new TranslatableString(getKey(@"failed_to_process_timestamp"), @"Failed to process timestamp"); + + /// + /// "The timestamp was too long to process" + /// + public static LocalisableString TooLongTimestamp => new TranslatableString(getKey(@"too_long_timestamp"), @"The timestamp was too long to process"); + private static string getKey(string key) => $@"{prefix}:{key}"; } } diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 2f11964f6a..439e112bd3 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -58,6 +58,7 @@ using osu.Game.Performance; using osu.Game.Rulesets.Mods; using osu.Game.Scoring; using osu.Game.Screens; +using osu.Game.Screens.Edit; using osu.Game.Screens.Menu; using osu.Game.Screens.OnlinePlay.Multiplayer; using osu.Game.Screens.Play; @@ -433,6 +434,9 @@ namespace osu.Game break; case LinkAction.OpenEditorTimestamp: + SeekToTimestamp(argString); + break; + case LinkAction.JoinMultiplayerMatch: case LinkAction.Spectate: waitForReady(() => Notifications, _ => Notifications.Post(new SimpleNotification @@ -550,6 +554,50 @@ namespace osu.Game /// The build version of the update stream public void ShowChangelogBuild(string updateStream, string version) => waitForReady(() => changelogOverlay, _ => changelogOverlay.ShowBuild(updateStream, version)); + /// + /// Seek to a given timestamp in the Editor and select relevant HitObjects if needed + /// + /// The timestamp and the selected objects + public void SeekToTimestamp(string timestamp) + { + if (ScreenStack.CurrentScreen is not Editor editor) + { + waitForReady(() => Notifications, _ => Notifications.Post(new SimpleErrorNotification + { + Text = EditorStrings.MustBeInEdit, + })); + return; + } + + string[] groups = EditorTimestampParser.GetRegexGroups(timestamp); + + if (groups.Length != 2 || string.IsNullOrEmpty(groups[0])) + { + waitForReady(() => Notifications, _ => Notifications.Post(new SimpleErrorNotification + { + Text = EditorStrings.FailedToProcessTimestamp + })); + return; + } + + string timeGroup = groups[0]; + string objectsGroup = groups[1]; + string timeMinutes = timeGroup.Split(':').FirstOrDefault() ?? string.Empty; + + // Currently, lazer chat highlights infinite-long editor links like `10000000000:00:000 (1)` + // Limit timestamp link length at 30000 min (50 hr) to avoid parsing issues + if (timeMinutes.Length > 5 || double.Parse(timeMinutes) > 30_000) + { + waitForReady(() => Notifications, _ => Notifications.Post(new SimpleErrorNotification + { + Text = EditorStrings.TooLongTimestamp + })); + return; + } + + editor.SeekToTimestamp(timeGroup, objectsGroup); + } + /// /// Present a skin select immediately. /// diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 91c3c98f01..b2fad55fed 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -39,6 +39,7 @@ using osu.Game.Overlays.Notifications; using osu.Game.Overlays.OSD; using osu.Game.Rulesets; using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Objects; using osu.Game.Screens.Edit.Components.Menus; using osu.Game.Screens.Edit.Compose; using osu.Game.Screens.Edit.Compose.Components.Timeline; @@ -1137,6 +1138,39 @@ namespace osu.Game.Screens.Edit loader?.CancelPendingDifficultySwitch(); } + public void SeekToTimestamp(string timeGroup, string objectsGroup) + { + double position = EditorTimestampParser.GetTotalMilliseconds(timeGroup); + editorBeatmap.SelectedHitObjects.Clear(); + + if (string.IsNullOrEmpty(objectsGroup)) + { + if (clock.IsRunning) + clock.Stop(); + + clock.Seek(position); + return; + } + + if (Mode.Value != EditorScreenMode.Compose) + Mode.Value = EditorScreenMode.Compose; + + // Seek to the next closest HitObject's position + HitObject nextObject = editorBeatmap.HitObjects.FirstOrDefault(x => x.StartTime >= position); + if (nextObject != null && nextObject.StartTime > 0) + position = nextObject.StartTime; + + List selected = EditorTimestampParser.GetSelectedHitObjects(editorBeatmap.HitObjects.ToList(), objectsGroup, position); + + if (selected.Any()) + editorBeatmap.SelectedHitObjects.AddRange(selected); + + if (clock.IsRunning) + clock.Stop(); + + clock.Seek(position); + } + public double SnapTime(double time, double? referenceTime) => editorBeatmap.SnapTime(time, referenceTime); public double GetBeatLengthAtTime(double referenceTime) => editorBeatmap.GetBeatLengthAtTime(referenceTime); diff --git a/osu.Game/Screens/Edit/EditorTimestampParser.cs b/osu.Game/Screens/Edit/EditorTimestampParser.cs new file mode 100644 index 0000000000..44d614ca70 --- /dev/null +++ b/osu.Game/Screens/Edit/EditorTimestampParser.cs @@ -0,0 +1,101 @@ +// 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.Diagnostics; +using System.Linq; +using System.Text.RegularExpressions; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Types; + +namespace osu.Game.Screens.Edit +{ + public static class EditorTimestampParser + { + private static readonly Regex timestamp_regex = new Regex(@"^(\d+:\d+:\d+)(?: \((\d+(?:[|,]\d+)*)\))?$", RegexOptions.Compiled); + + public static string[] GetRegexGroups(string timestamp) + { + Match match = timestamp_regex.Match(timestamp); + return match.Success + ? match.Groups.Values.Where(x => x is not Match).Select(x => x.Value).ToArray() + : Array.Empty(); + } + + public static double GetTotalMilliseconds(string timeGroup) + { + int[] times = timeGroup.Split(':').Select(int.Parse).ToArray(); + + Debug.Assert(times.Length == 3); + + return (times[0] * 60 + times[1]) * 1_000 + times[2]; + } + + public static List GetSelectedHitObjects(IEnumerable editorHitObjects, string objectsGroup, double position) + { + List hitObjects = editorHitObjects.Where(x => x.StartTime >= position).ToList(); + List selectedObjects = new List(); + + string[] objectsToSelect = objectsGroup.Split(',').ToArray(); + + foreach (string objectInfo in objectsToSelect) + { + HitObject? current = hitObjects.FirstOrDefault(x => shouldHitObjectBeSelected(x, objectInfo)); + + if (current == null) + continue; + + selectedObjects.Add(current); + hitObjects = hitObjects.Where(x => x != current && x.StartTime >= current.StartTime).ToList(); + } + + // Stable behavior + // - always selects next closest object when `objectsGroup` only has one, non-Column item + if (objectsToSelect.Length != 1 || objectsGroup.Contains('|')) + return selectedObjects; + + HitObject? nextClosest = editorHitObjects.FirstOrDefault(x => x.StartTime >= position); + if (nextClosest == null) + return selectedObjects; + + if (nextClosest.StartTime <= (selectedObjects.FirstOrDefault()?.StartTime ?? position)) + { + selectedObjects.Clear(); + selectedObjects.Add(nextClosest); + } + + return selectedObjects; + } + + private static bool shouldHitObjectBeSelected(HitObject hitObject, string objectInfo) + { + switch (hitObject) + { + // (combo) + case IHasComboInformation comboInfo: + { + if (!double.TryParse(objectInfo, out double comboValue) || comboValue < 1) + return false; + + return comboInfo.IndexInCurrentCombo + 1 == comboValue; + } + + // (time|column) + case IHasColumn column: + { + double[] split = objectInfo.Split('|').Select(double.Parse).ToArray(); + if (split.Length != 2) + return false; + + double timeValue = split[0]; + double columnValue = split[1]; + return hitObject.StartTime == timeValue && column.Column == columnValue; + } + + default: + return false; + } + } + } +} From f854e78bb03f6117b1cfb8b0579e867d1aae093f Mon Sep 17 00:00:00 2001 From: ratinfx Date: Sat, 4 Nov 2023 03:29:05 +0100 Subject: [PATCH 02/91] Added ExclamationTriangle Icon to notifications --- osu.Game/OsuGame.cs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 439e112bd3..5acd958568 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -562,8 +562,9 @@ namespace osu.Game { if (ScreenStack.CurrentScreen is not Editor editor) { - waitForReady(() => Notifications, _ => Notifications.Post(new SimpleErrorNotification + waitForReady(() => Notifications, _ => Notifications.Post(new SimpleNotification { + Icon = FontAwesome.Solid.ExclamationTriangle, Text = EditorStrings.MustBeInEdit, })); return; @@ -573,8 +574,9 @@ namespace osu.Game if (groups.Length != 2 || string.IsNullOrEmpty(groups[0])) { - waitForReady(() => Notifications, _ => Notifications.Post(new SimpleErrorNotification + waitForReady(() => Notifications, _ => Notifications.Post(new SimpleNotification { + Icon = FontAwesome.Solid.ExclamationTriangle, Text = EditorStrings.FailedToProcessTimestamp })); return; @@ -588,8 +590,9 @@ namespace osu.Game // Limit timestamp link length at 30000 min (50 hr) to avoid parsing issues if (timeMinutes.Length > 5 || double.Parse(timeMinutes) > 30_000) { - waitForReady(() => Notifications, _ => Notifications.Post(new SimpleErrorNotification + waitForReady(() => Notifications, _ => Notifications.Post(new SimpleNotification { + Icon = FontAwesome.Solid.ExclamationTriangle, Text = EditorStrings.TooLongTimestamp })); return; From 60f62faec3712e1086af4d0a7462c60d0efbc257 Mon Sep 17 00:00:00 2001 From: ratinfx Date: Sat, 4 Nov 2023 03:30:38 +0100 Subject: [PATCH 03/91] Renamed Editor method --- osu.Game/OsuGame.cs | 2 +- osu.Game/Screens/Edit/Editor.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 5acd958568..a6bb6cc120 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -598,7 +598,7 @@ namespace osu.Game return; } - editor.SeekToTimestamp(timeGroup, objectsGroup); + editor.SeekAndSelectHitObjects(timeGroup, objectsGroup); } /// diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index b2fad55fed..80e01d4eb7 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -1138,7 +1138,7 @@ namespace osu.Game.Screens.Edit loader?.CancelPendingDifficultySwitch(); } - public void SeekToTimestamp(string timeGroup, string objectsGroup) + public void SeekAndSelectHitObjects(string timeGroup, string objectsGroup) { double position = EditorTimestampParser.GetTotalMilliseconds(timeGroup); editorBeatmap.SelectedHitObjects.Clear(); From f867cff8c79c55f75708ea41c699c6d7a557485c Mon Sep 17 00:00:00 2001 From: ratinfx Date: Sat, 4 Nov 2023 03:42:08 +0100 Subject: [PATCH 04/91] Added OpenEditorTimestamp Tests --- .../Editing/TestSceneOpenEditorTimestamp.cs | 377 ++++++++++++++++++ 1 file changed, 377 insertions(+) create mode 100644 osu.Game.Tests/Visual/Editing/TestSceneOpenEditorTimestamp.cs diff --git a/osu.Game.Tests/Visual/Editing/TestSceneOpenEditorTimestamp.cs b/osu.Game.Tests/Visual/Editing/TestSceneOpenEditorTimestamp.cs new file mode 100644 index 0000000000..5ae0a20fd2 --- /dev/null +++ b/osu.Game.Tests/Visual/Editing/TestSceneOpenEditorTimestamp.cs @@ -0,0 +1,377 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +#nullable disable + +using System.Collections.Generic; +using System.Linq; +using NUnit.Framework; +using osu.Framework.Extensions; +using osu.Framework.Extensions.ObjectExtensions; +using osu.Framework.Testing; +using osu.Game.Beatmaps; +using osu.Game.Database; +using osu.Game.Localisation; +using osu.Game.Online.Chat; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Mania; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Types; +using osu.Game.Rulesets.Osu; +using osu.Game.Screens.Edit; +using osu.Game.Screens.Menu; +using osu.Game.Screens.Select; +using osu.Game.Tests.Resources; + +namespace osu.Game.Tests.Visual.Editing +{ + public partial class TestSceneOpenEditorTimestamp : OsuGameTestScene + { + protected Editor Editor => (Editor)Game.ScreenStack.CurrentScreen; + protected EditorBeatmap EditorBeatmap => Editor.ChildrenOfType().Single(); + protected EditorClock EditorClock => Editor.ChildrenOfType().Single(); + + protected void AddStepClickLink(string timestamp, string step = "") + { + AddStep(step + timestamp, () => + Game.HandleLink(new LinkDetails(LinkAction.OpenEditorTimestamp, timestamp)) + ); + } + + protected void AddStepScreenModeTo(EditorScreenMode screenMode) + { + AddStep("change screen to " + screenMode, () => Editor.Mode.Value = screenMode); + } + + protected void AssertOnScreenAt(EditorScreenMode screen, double time, string text = "stays in") + { + AddAssert($"{text} {screen} at {time}", () => + Editor.Mode.Value == screen + && EditorClock.CurrentTime == time + ); + } + + protected bool HasCombosInOrder(IEnumerable selected, params int[] comboNumbers) + { + List hitObjects = selected.ToList(); + if (hitObjects.Count != comboNumbers.Length) + return false; + + return !hitObjects.Select(x => (IHasComboInformation)x) + .Where((combo, i) => combo.IndexInCurrentCombo + 1 != comboNumbers[i]) + .Any(); + } + + protected bool IsNoteAt(HitObject hitObject, double time, int column) + { + return hitObject is IHasColumn columnInfo + && hitObject.StartTime == time + && columnInfo.Column == column; + } + + public void SetUpEditor(RulesetInfo ruleset) + { + BeatmapSetInfo beatmapSet = null!; + + AddStep("Import test beatmap", () => + Game.BeatmapManager.Import(TestResources.GetTestBeatmapForImport()).WaitSafely() + ); + AddStep("Retrieve beatmap", () => + beatmapSet = Game.BeatmapManager.QueryBeatmapSet(set => !set.Protected).AsNonNull().Value.Detach() + ); + AddStep("Present beatmap", () => Game.PresentBeatmap(beatmapSet)); + AddUntilStep("Wait for song select", () => + Game.Beatmap.Value.BeatmapSetInfo.Equals(beatmapSet) + && Game.ScreenStack.CurrentScreen is PlaySongSelect songSelect + && songSelect.IsLoaded + ); + AddStep("Switch ruleset", () => Game.Ruleset.Value = ruleset); + AddStep("Open editor for ruleset", () => + ((PlaySongSelect)Game.ScreenStack.CurrentScreen) + .Edit(beatmapSet.Beatmaps.Last(beatmap => beatmap.Ruleset.Name == ruleset.Name)) + ); + AddUntilStep("Wait for editor open", () => Editor.ReadyForUse); + } + + [Test] + public void TestErrorNotifications() + { + RulesetInfo rulesetInfo = new OsuRuleset().RulesetInfo; + + AddStepClickLink("00:00:000"); + AddAssert("recieved 'must be in edit'", () => + Game.Notifications.UnreadCount.Value == 1 + && Game.Notifications.AllNotifications.Count(x => x.Text == EditorStrings.MustBeInEdit) == 1 + ); + + AddStep("enter song select", () => Game.ChildrenOfType().Single().OnSolo.Invoke()); + AddAssert("entered song select", () => Game.ScreenStack.CurrentScreen is PlaySongSelect); + + AddStepClickLink("00:00:000 (1)"); + AddAssert("recieved 'must be in edit'", () => + Game.Notifications.UnreadCount.Value == 2 + && Game.Notifications.AllNotifications.Count(x => x.Text == EditorStrings.MustBeInEdit) == 2 + ); + + SetUpEditor(rulesetInfo); + AddAssert("is editor Osu", () => EditorBeatmap.BeatmapInfo.Ruleset.Equals(rulesetInfo)); + + AddStepClickLink("00:000", "invalid link "); + AddAssert("recieved 'failed to process'", () => + Game.Notifications.AllNotifications.Count(x => x.Text == EditorStrings.FailedToProcessTimestamp) == 1 + ); + + AddStepClickLink("00:00:00:000", "invalid link "); + AddAssert("recieved 'failed to process'", () => + Game.Notifications.AllNotifications.Count(x => x.Text == EditorStrings.FailedToProcessTimestamp) == 2 + ); + + AddStepClickLink("00:00:000 ()", "invalid link "); + AddAssert("recieved 'failed to process'", () => + Game.Notifications.AllNotifications.Count(x => x.Text == EditorStrings.FailedToProcessTimestamp) == 3 + ); + + AddStepClickLink("00:00:000 (-1)", "invalid link "); + AddAssert("recieved 'failed to process'", () => + Game.Notifications.AllNotifications.Count(x => x.Text == EditorStrings.FailedToProcessTimestamp) == 4 + ); + + AddStepClickLink("50000:00:000", "too long link "); + AddAssert("recieved 'too long'", () => + EditorClock.CurrentTime == 0 + && Game.Notifications.AllNotifications.Count(x => x.Text == EditorStrings.TooLongTimestamp) == 1 + ); + } + + [Test] + public void TestHandleCurrentScreenChanges() + { + const long long_link_value = 1_000 * 60 * 1_000; + RulesetInfo rulesetInfo = new OsuRuleset().RulesetInfo; + + SetUpEditor(rulesetInfo); + AddAssert("is editor Osu", () => EditorBeatmap.BeatmapInfo.Ruleset.Equals(rulesetInfo)); + + AddStepClickLink("1000:00:000", "long link "); + AddAssert("moved to end of track", () => + EditorClock.CurrentTime == long_link_value + || (EditorClock.TrackLength < long_link_value && EditorClock.CurrentTime == EditorClock.TrackLength) + ); + + AddStepScreenModeTo(EditorScreenMode.SongSetup); + AddStepClickLink("00:00:000"); + AssertOnScreenAt(EditorScreenMode.SongSetup, 0); + + AddStepClickLink("00:05:000 (0|0)"); + AddAssert("seek and change screen", () => + Editor.Mode.Value == EditorScreenMode.Compose + && EditorClock.CurrentTime == EditorBeatmap.HitObjects.First(x => x.StartTime >= 5_000).StartTime + ); + + AddStepScreenModeTo(EditorScreenMode.Design); + AddStepClickLink("00:10:000"); + AssertOnScreenAt(EditorScreenMode.Design, 10_000); + + AddStepClickLink("00:15:000 (1)"); + AddAssert("seek and change screen", () => + Editor.Mode.Value == EditorScreenMode.Compose + && EditorClock.CurrentTime == EditorBeatmap.HitObjects.First(x => x.StartTime >= 15_000).StartTime + ); + + AddStepScreenModeTo(EditorScreenMode.Timing); + AddStepClickLink("00:20:000"); + AssertOnScreenAt(EditorScreenMode.Timing, 20_000); + + AddStepClickLink("00:25:000 (0,1)"); + AddAssert("seek and change screen", () => + Editor.Mode.Value == EditorScreenMode.Compose + && EditorClock.CurrentTime == EditorBeatmap.HitObjects.First(x => x.StartTime >= 25_000).StartTime + ); + + AddStepScreenModeTo(EditorScreenMode.Verify); + AddStepClickLink("00:30:000"); + AssertOnScreenAt(EditorScreenMode.Verify, 30_000); + + AddStepClickLink("00:35:000 (0,1)"); + AddAssert("seek and change screen", () => + Editor.Mode.Value == EditorScreenMode.Compose + && EditorClock.CurrentTime == EditorBeatmap.HitObjects.First(x => x.StartTime >= 35_000).StartTime + ); + + AddStepClickLink("00:00:000"); + AssertOnScreenAt(EditorScreenMode.Compose, 0); + } + + [Test] + public void TestSelectionForOsu() + { + HitObject firstObject = null!; + RulesetInfo rulesetInfo = new OsuRuleset().RulesetInfo; + + SetUpEditor(rulesetInfo); + AddAssert("is editor Osu", () => EditorBeatmap.BeatmapInfo.Ruleset.Equals(rulesetInfo)); + + AddStepClickLink("00:00:956 (1,2,3)"); + AddAssert("snap and select 1-2-3", () => + { + firstObject = EditorBeatmap.HitObjects.First(); + return EditorClock.CurrentTime == firstObject.StartTime + && EditorBeatmap.SelectedHitObjects.Count == 3 + && HasCombosInOrder(EditorBeatmap.SelectedHitObjects, 1, 2, 3); + }); + + AddStepClickLink("00:01:450 (2,3,4,1,2)"); + AddAssert("snap and select 2-3-4-1-2", () => + EditorClock.CurrentTime == 1_450 + && EditorBeatmap.SelectedHitObjects.Count == 5 + && HasCombosInOrder(EditorBeatmap.SelectedHitObjects, 2, 3, 4, 1, 2) + ); + + AddStepClickLink("00:00:956 (1,1,1)"); + AddAssert("snap and select 1-1-1", () => + EditorClock.CurrentTime == firstObject.StartTime + && EditorBeatmap.SelectedHitObjects.Count == 3 + && HasCombosInOrder(EditorBeatmap.SelectedHitObjects, 1, 1, 1) + ); + } + + [Test] + public void TestUnusualSelectionForOsu() + { + HitObject firstObject = null!; + RulesetInfo rulesetInfo = new OsuRuleset().RulesetInfo; + + SetUpEditor(rulesetInfo); + AddAssert("is editor Osu", () => EditorBeatmap.BeatmapInfo.Ruleset.Equals(rulesetInfo)); + + AddStepClickLink("00:00:000 (1,2,3)", "invalid offset "); + AddAssert("snap to next, select 1-2-3", () => + { + firstObject = EditorBeatmap.HitObjects.First(); + return EditorClock.CurrentTime == firstObject.StartTime + && EditorBeatmap.SelectedHitObjects.Count == 3 + && HasCombosInOrder(EditorBeatmap.SelectedHitObjects, 1, 2, 3); + }); + + AddStepClickLink("00:00:956 (2,3,4)", "invalid offset "); + AddAssert("snap to next, select 2-3-4", () => + EditorClock.CurrentTime == EditorBeatmap.HitObjects.First(x => x.StartTime >= 956).StartTime + && EditorBeatmap.SelectedHitObjects.Count == 3 + && HasCombosInOrder(EditorBeatmap.SelectedHitObjects, 2, 3, 4) + ); + + AddStepClickLink("00:00:000 (0)", "invalid combo "); + AddAssert("snap to 1, select 1", () => + EditorClock.CurrentTime == firstObject.StartTime + && EditorBeatmap.SelectedHitObjects.Count == 1 + && HasCombosInOrder(EditorBeatmap.SelectedHitObjects, 1) + ); + + AddStepClickLink("00:00:000 (1)", "invalid offset "); + AddAssert("snap and select 1", () => + EditorClock.CurrentTime == firstObject.StartTime + && EditorBeatmap.SelectedHitObjects.Count == 1 + && HasCombosInOrder(EditorBeatmap.SelectedHitObjects, 1) + ); + + AddStepClickLink("00:00:000 (2)", "invalid offset "); + AddAssert("snap and select 1", () => + EditorClock.CurrentTime == firstObject.StartTime + && EditorBeatmap.SelectedHitObjects.Count == 1 + && HasCombosInOrder(EditorBeatmap.SelectedHitObjects, 1) + ); + + AddStepClickLink("00:00:000 (2,3)", "invalid offset "); + AddAssert("snap to 1, select 2-3", () => + EditorClock.CurrentTime == firstObject.StartTime + && EditorBeatmap.SelectedHitObjects.Count == 2 + && HasCombosInOrder(EditorBeatmap.SelectedHitObjects, 2, 3) + ); + + AddStepClickLink("00:00:956 (956|1,956|2)", "mania link "); + AddAssert("snap to next, select none", () => + EditorClock.CurrentTime == firstObject?.StartTime + && !EditorBeatmap.SelectedHitObjects.Any() + ); + + AddStepClickLink("00:00:000 (0|1)", "mania link "); + AddAssert("snap to 1, select none", () => + EditorClock.CurrentTime == firstObject.StartTime + && !EditorBeatmap.SelectedHitObjects.Any() + ); + } + + [Test] + public void TestSelectionForMania() + { + RulesetInfo rulesetInfo = new ManiaRuleset().RulesetInfo; + + SetUpEditor(rulesetInfo); + AddAssert("is editor Mania", () => EditorBeatmap.BeatmapInfo.Ruleset.Equals(rulesetInfo)); + + AddStepClickLink("00:11:010 (11010|1,11175|5,11258|3,11340|5,11505|1)"); + AddAssert("selected group", () => + EditorClock.CurrentTime == 11010 + && EditorBeatmap.SelectedHitObjects.Count == 5 + && EditorBeatmap.SelectedHitObjects.All(x => + IsNoteAt(x, 11010, 1) || IsNoteAt(x, 11175, 5) || + IsNoteAt(x, 11258, 3) || IsNoteAt(x, 11340, 5) || + IsNoteAt(x, 11505, 1)) + ); + + AddStepClickLink("00:00:956 (956|1,956|6,1285|3,1780|4)"); + AddAssert("selected ungrouped", () => + EditorClock.CurrentTime == 956 + && EditorBeatmap.SelectedHitObjects.Count == 4 + && EditorBeatmap.SelectedHitObjects.All(x => + IsNoteAt(x, 956, 1) || IsNoteAt(x, 956, 6) || + IsNoteAt(x, 1285, 3) || IsNoteAt(x, 1780, 4)) + ); + + AddStepClickLink("02:36:560 (156560|1,156560|4,156560|6)"); + AddAssert("selected in row", () => + EditorClock.CurrentTime == 156560 + && EditorBeatmap.SelectedHitObjects.Count == 3 + && EditorBeatmap.SelectedHitObjects.All(x => + IsNoteAt(x, 156560, 1) || IsNoteAt(x, 156560, 4) || + IsNoteAt(x, 156560, 6)) + ); + + AddStepClickLink("00:35:736 (35736|3,36395|3,36725|3,37384|3)"); + AddAssert("selected in column", () => + EditorClock.CurrentTime == 35736 + && EditorBeatmap.SelectedHitObjects.Count == 4 + && EditorBeatmap.SelectedHitObjects.All(x => + IsNoteAt(x, 35736, 3) || IsNoteAt(x, 36395, 3) || + IsNoteAt(x, 36725, 3) || IsNoteAt(x, 37384, 3)) + ); + } + + [Test] + public void TestUnusualSelectionForMania() + { + RulesetInfo rulesetInfo = new ManiaRuleset().RulesetInfo; + + SetUpEditor(rulesetInfo); + AddAssert("is editor Mania", () => EditorBeatmap.BeatmapInfo.Ruleset.Equals(rulesetInfo)); + + AddStepClickLink("00:00:000 (0|1)", "invalid link "); + AddAssert("snap to 1, select none", () => + EditorClock.CurrentTime == 956 + && !EditorBeatmap.SelectedHitObjects.Any() + ); + + AddStepClickLink("00:00:000 (0)", "std link "); + AddAssert("snap and select 1", () => + EditorClock.CurrentTime == 956 + && EditorBeatmap.SelectedHitObjects.All(x => IsNoteAt(x, 956, 1)) + ); + + AddStepClickLink("00:00:000 (1,2)", "std link "); + AddAssert("snap to 1, select none", () => + EditorClock.CurrentTime == 956 + && !EditorBeatmap.SelectedHitObjects.Any() + ); + } + } +} From 19cdf99df8f101b5c676528826a39cb905c1efe5 Mon Sep 17 00:00:00 2001 From: ratinfx Date: Sat, 4 Nov 2023 12:04:58 +0100 Subject: [PATCH 05/91] Fixed up tests --- .../Editing/TestSceneOpenEditorTimestamp.cs | 216 +++++++----------- 1 file changed, 83 insertions(+), 133 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneOpenEditorTimestamp.cs b/osu.Game.Tests/Visual/Editing/TestSceneOpenEditorTimestamp.cs index 5ae0a20fd2..fea9334ff8 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneOpenEditorTimestamp.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneOpenEditorTimestamp.cs @@ -33,7 +33,7 @@ namespace osu.Game.Tests.Visual.Editing protected void AddStepClickLink(string timestamp, string step = "") { - AddStep(step + timestamp, () => + AddStep($"{step} {timestamp}", () => Game.HandleLink(new LinkDetails(LinkAction.OpenEditorTimestamp, timestamp)) ); } @@ -43,7 +43,7 @@ namespace osu.Game.Tests.Visual.Editing AddStep("change screen to " + screenMode, () => Editor.Mode.Value = screenMode); } - protected void AssertOnScreenAt(EditorScreenMode screen, double time, string text = "stays in") + protected void AssertOnScreenAt(EditorScreenMode screen, double time, string text = "stayed in") { AddAssert($"{text} {screen} at {time}", () => Editor.Mode.Value == screen @@ -51,7 +51,34 @@ namespace osu.Game.Tests.Visual.Editing ); } - protected bool HasCombosInOrder(IEnumerable selected, params int[] comboNumbers) + protected void AssertMovedScreenTo(EditorScreenMode screen, string text = "moved to") + { + AddAssert($"{text} {screen}", () => Editor.Mode.Value == screen); + } + + private bool checkSnapAndSelectCombo(double startTime, params int[] comboNumbers) + { + bool checkCombos = comboNumbers.Any() + ? hasCombosInOrder(EditorBeatmap.SelectedHitObjects, comboNumbers) + : !EditorBeatmap.SelectedHitObjects.Any(); + + return EditorClock.CurrentTime == startTime + && EditorBeatmap.SelectedHitObjects.Count == comboNumbers.Length + && checkCombos; + } + + private bool checkSnapAndSelectColumn(double startTime, List<(int, int)> columnPairs = null) + { + bool checkColumns = columnPairs != null + ? EditorBeatmap.SelectedHitObjects.All(x => columnPairs.Any(col => isNoteAt(x, col.Item1, col.Item2))) + : !EditorBeatmap.SelectedHitObjects.Any(); + + return EditorClock.CurrentTime == startTime + && EditorBeatmap.SelectedHitObjects.Count == (columnPairs?.Count ?? 0) + && checkColumns; + } + + private bool hasCombosInOrder(IEnumerable selected, params int[] comboNumbers) { List hitObjects = selected.ToList(); if (hitObjects.Count != comboNumbers.Length) @@ -62,7 +89,7 @@ namespace osu.Game.Tests.Visual.Editing .Any(); } - protected bool IsNoteAt(HitObject hitObject, double time, int column) + private bool isNoteAt(HitObject hitObject, double time, int column) { return hitObject is IHasColumn columnInfo && hitObject.StartTime == time @@ -100,8 +127,7 @@ namespace osu.Game.Tests.Visual.Editing AddStepClickLink("00:00:000"); AddAssert("recieved 'must be in edit'", () => - Game.Notifications.UnreadCount.Value == 1 - && Game.Notifications.AllNotifications.Count(x => x.Text == EditorStrings.MustBeInEdit) == 1 + Game.Notifications.AllNotifications.Count(x => x.Text == EditorStrings.MustBeInEdit) == 1 ); AddStep("enter song select", () => Game.ChildrenOfType().Single().OnSolo.Invoke()); @@ -109,37 +135,35 @@ namespace osu.Game.Tests.Visual.Editing AddStepClickLink("00:00:000 (1)"); AddAssert("recieved 'must be in edit'", () => - Game.Notifications.UnreadCount.Value == 2 - && Game.Notifications.AllNotifications.Count(x => x.Text == EditorStrings.MustBeInEdit) == 2 + Game.Notifications.AllNotifications.Count(x => x.Text == EditorStrings.MustBeInEdit) == 2 ); SetUpEditor(rulesetInfo); AddAssert("is editor Osu", () => EditorBeatmap.BeatmapInfo.Ruleset.Equals(rulesetInfo)); - AddStepClickLink("00:000", "invalid link "); + AddStepClickLink("00:000", "invalid link"); AddAssert("recieved 'failed to process'", () => Game.Notifications.AllNotifications.Count(x => x.Text == EditorStrings.FailedToProcessTimestamp) == 1 ); - AddStepClickLink("00:00:00:000", "invalid link "); + AddStepClickLink("00:00:00:000", "invalid link"); AddAssert("recieved 'failed to process'", () => Game.Notifications.AllNotifications.Count(x => x.Text == EditorStrings.FailedToProcessTimestamp) == 2 ); - AddStepClickLink("00:00:000 ()", "invalid link "); + AddStepClickLink("00:00:000 ()", "invalid link"); AddAssert("recieved 'failed to process'", () => Game.Notifications.AllNotifications.Count(x => x.Text == EditorStrings.FailedToProcessTimestamp) == 3 ); - AddStepClickLink("00:00:000 (-1)", "invalid link "); + AddStepClickLink("00:00:000 (-1)", "invalid link"); AddAssert("recieved 'failed to process'", () => Game.Notifications.AllNotifications.Count(x => x.Text == EditorStrings.FailedToProcessTimestamp) == 4 ); - AddStepClickLink("50000:00:000", "too long link "); + AddStepClickLink("50000:00:000", "too long link"); AddAssert("recieved 'too long'", () => - EditorClock.CurrentTime == 0 - && Game.Notifications.AllNotifications.Count(x => x.Text == EditorStrings.TooLongTimestamp) == 1 + Game.Notifications.AllNotifications.Count(x => x.Text == EditorStrings.TooLongTimestamp) == 1 ); } @@ -152,7 +176,7 @@ namespace osu.Game.Tests.Visual.Editing SetUpEditor(rulesetInfo); AddAssert("is editor Osu", () => EditorBeatmap.BeatmapInfo.Ruleset.Equals(rulesetInfo)); - AddStepClickLink("1000:00:000", "long link "); + AddStepClickLink("1000:00:000", "long link"); AddAssert("moved to end of track", () => EditorClock.CurrentTime == long_link_value || (EditorClock.TrackLength < long_link_value && EditorClock.CurrentTime == EditorClock.TrackLength) @@ -163,40 +187,28 @@ namespace osu.Game.Tests.Visual.Editing AssertOnScreenAt(EditorScreenMode.SongSetup, 0); AddStepClickLink("00:05:000 (0|0)"); - AddAssert("seek and change screen", () => - Editor.Mode.Value == EditorScreenMode.Compose - && EditorClock.CurrentTime == EditorBeatmap.HitObjects.First(x => x.StartTime >= 5_000).StartTime - ); + AssertMovedScreenTo(EditorScreenMode.Compose); AddStepScreenModeTo(EditorScreenMode.Design); AddStepClickLink("00:10:000"); AssertOnScreenAt(EditorScreenMode.Design, 10_000); AddStepClickLink("00:15:000 (1)"); - AddAssert("seek and change screen", () => - Editor.Mode.Value == EditorScreenMode.Compose - && EditorClock.CurrentTime == EditorBeatmap.HitObjects.First(x => x.StartTime >= 15_000).StartTime - ); + AssertMovedScreenTo(EditorScreenMode.Compose); AddStepScreenModeTo(EditorScreenMode.Timing); AddStepClickLink("00:20:000"); AssertOnScreenAt(EditorScreenMode.Timing, 20_000); AddStepClickLink("00:25:000 (0,1)"); - AddAssert("seek and change screen", () => - Editor.Mode.Value == EditorScreenMode.Compose - && EditorClock.CurrentTime == EditorBeatmap.HitObjects.First(x => x.StartTime >= 25_000).StartTime - ); + AssertMovedScreenTo(EditorScreenMode.Compose); AddStepScreenModeTo(EditorScreenMode.Verify); AddStepClickLink("00:30:000"); AssertOnScreenAt(EditorScreenMode.Verify, 30_000); AddStepClickLink("00:35:000 (0,1)"); - AddAssert("seek and change screen", () => - Editor.Mode.Value == EditorScreenMode.Compose - && EditorClock.CurrentTime == EditorBeatmap.HitObjects.First(x => x.StartTime >= 35_000).StartTime - ); + AssertMovedScreenTo(EditorScreenMode.Compose); AddStepClickLink("00:00:000"); AssertOnScreenAt(EditorScreenMode.Compose, 0); @@ -215,24 +227,14 @@ namespace osu.Game.Tests.Visual.Editing AddAssert("snap and select 1-2-3", () => { firstObject = EditorBeatmap.HitObjects.First(); - return EditorClock.CurrentTime == firstObject.StartTime - && EditorBeatmap.SelectedHitObjects.Count == 3 - && HasCombosInOrder(EditorBeatmap.SelectedHitObjects, 1, 2, 3); + return checkSnapAndSelectCombo(firstObject.StartTime, 1, 2, 3); }); AddStepClickLink("00:01:450 (2,3,4,1,2)"); - AddAssert("snap and select 2-3-4-1-2", () => - EditorClock.CurrentTime == 1_450 - && EditorBeatmap.SelectedHitObjects.Count == 5 - && HasCombosInOrder(EditorBeatmap.SelectedHitObjects, 2, 3, 4, 1, 2) - ); + AddAssert("snap and select 2-3-4-1-2", () => checkSnapAndSelectCombo(1_450, 2, 3, 4, 1, 2)); AddStepClickLink("00:00:956 (1,1,1)"); - AddAssert("snap and select 1-1-1", () => - EditorClock.CurrentTime == firstObject.StartTime - && EditorBeatmap.SelectedHitObjects.Count == 3 - && HasCombosInOrder(EditorBeatmap.SelectedHitObjects, 1, 1, 1) - ); + AddAssert("snap and select 1-1-1", () => checkSnapAndSelectCombo(firstObject.StartTime, 1, 1, 1)); } [Test] @@ -244,61 +246,33 @@ namespace osu.Game.Tests.Visual.Editing SetUpEditor(rulesetInfo); AddAssert("is editor Osu", () => EditorBeatmap.BeatmapInfo.Ruleset.Equals(rulesetInfo)); - AddStepClickLink("00:00:000 (1,2,3)", "invalid offset "); + AddStepClickLink("00:00:000 (1,2,3)", "invalid offset"); AddAssert("snap to next, select 1-2-3", () => { firstObject = EditorBeatmap.HitObjects.First(); - return EditorClock.CurrentTime == firstObject.StartTime - && EditorBeatmap.SelectedHitObjects.Count == 3 - && HasCombosInOrder(EditorBeatmap.SelectedHitObjects, 1, 2, 3); + return checkSnapAndSelectCombo(firstObject.StartTime, 1, 2, 3); }); - AddStepClickLink("00:00:956 (2,3,4)", "invalid offset "); - AddAssert("snap to next, select 2-3-4", () => - EditorClock.CurrentTime == EditorBeatmap.HitObjects.First(x => x.StartTime >= 956).StartTime - && EditorBeatmap.SelectedHitObjects.Count == 3 - && HasCombosInOrder(EditorBeatmap.SelectedHitObjects, 2, 3, 4) - ); + AddStepClickLink("00:00:956 (2,3,4)", "invalid offset"); + AddAssert("snap to next, select 2-3-4", () => checkSnapAndSelectCombo(firstObject.StartTime, 2, 3, 4)); - AddStepClickLink("00:00:000 (0)", "invalid combo "); - AddAssert("snap to 1, select 1", () => - EditorClock.CurrentTime == firstObject.StartTime - && EditorBeatmap.SelectedHitObjects.Count == 1 - && HasCombosInOrder(EditorBeatmap.SelectedHitObjects, 1) - ); + AddStepClickLink("00:00:000 (0)", "invalid offset"); + AddAssert("snap and select 1", () => checkSnapAndSelectCombo(firstObject.StartTime, 1)); - AddStepClickLink("00:00:000 (1)", "invalid offset "); - AddAssert("snap and select 1", () => - EditorClock.CurrentTime == firstObject.StartTime - && EditorBeatmap.SelectedHitObjects.Count == 1 - && HasCombosInOrder(EditorBeatmap.SelectedHitObjects, 1) - ); + AddStepClickLink("00:00:000 (1)", "invalid offset"); + AddAssert("snap and select 1", () => checkSnapAndSelectCombo(firstObject.StartTime, 1)); - AddStepClickLink("00:00:000 (2)", "invalid offset "); - AddAssert("snap and select 1", () => - EditorClock.CurrentTime == firstObject.StartTime - && EditorBeatmap.SelectedHitObjects.Count == 1 - && HasCombosInOrder(EditorBeatmap.SelectedHitObjects, 1) - ); + AddStepClickLink("00:00:000 (2)", "invalid offset"); + AddAssert("snap and select 1", () => checkSnapAndSelectCombo(firstObject.StartTime, 1)); - AddStepClickLink("00:00:000 (2,3)", "invalid offset "); - AddAssert("snap to 1, select 2-3", () => - EditorClock.CurrentTime == firstObject.StartTime - && EditorBeatmap.SelectedHitObjects.Count == 2 - && HasCombosInOrder(EditorBeatmap.SelectedHitObjects, 2, 3) - ); + AddStepClickLink("00:00:000 (2,3)", "invalid offset"); + AddAssert("snap to 1, select 2-3", () => checkSnapAndSelectCombo(firstObject.StartTime, 2, 3)); - AddStepClickLink("00:00:956 (956|1,956|2)", "mania link "); - AddAssert("snap to next, select none", () => - EditorClock.CurrentTime == firstObject?.StartTime - && !EditorBeatmap.SelectedHitObjects.Any() - ); + AddStepClickLink("00:00:956 (956|1,956|2)", "mania link"); + AddAssert("snap to next, select none", () => checkSnapAndSelectCombo(firstObject.StartTime)); - AddStepClickLink("00:00:000 (0|1)", "mania link "); - AddAssert("snap to 1, select none", () => - EditorClock.CurrentTime == firstObject.StartTime - && !EditorBeatmap.SelectedHitObjects.Any() - ); + AddStepClickLink("00:00:000 (0|1)", "mania link"); + AddAssert("snap to 1, select none", () => checkSnapAndSelectCombo(firstObject.StartTime)); } [Test] @@ -310,41 +284,24 @@ namespace osu.Game.Tests.Visual.Editing AddAssert("is editor Mania", () => EditorBeatmap.BeatmapInfo.Ruleset.Equals(rulesetInfo)); AddStepClickLink("00:11:010 (11010|1,11175|5,11258|3,11340|5,11505|1)"); - AddAssert("selected group", () => - EditorClock.CurrentTime == 11010 - && EditorBeatmap.SelectedHitObjects.Count == 5 - && EditorBeatmap.SelectedHitObjects.All(x => - IsNoteAt(x, 11010, 1) || IsNoteAt(x, 11175, 5) || - IsNoteAt(x, 11258, 3) || IsNoteAt(x, 11340, 5) || - IsNoteAt(x, 11505, 1)) - ); + AddAssert("selected group", () => checkSnapAndSelectColumn(11010, new List<(int, int)> + { (11010, 1), (11175, 5), (11258, 3), (11340, 5), (11505, 1) } + )); AddStepClickLink("00:00:956 (956|1,956|6,1285|3,1780|4)"); - AddAssert("selected ungrouped", () => - EditorClock.CurrentTime == 956 - && EditorBeatmap.SelectedHitObjects.Count == 4 - && EditorBeatmap.SelectedHitObjects.All(x => - IsNoteAt(x, 956, 1) || IsNoteAt(x, 956, 6) || - IsNoteAt(x, 1285, 3) || IsNoteAt(x, 1780, 4)) - ); + AddAssert("selected ungrouped", () => checkSnapAndSelectColumn(956, new List<(int, int)> + { (956, 1), (956, 6), (1285, 3), (1780, 4) } + )); AddStepClickLink("02:36:560 (156560|1,156560|4,156560|6)"); - AddAssert("selected in row", () => - EditorClock.CurrentTime == 156560 - && EditorBeatmap.SelectedHitObjects.Count == 3 - && EditorBeatmap.SelectedHitObjects.All(x => - IsNoteAt(x, 156560, 1) || IsNoteAt(x, 156560, 4) || - IsNoteAt(x, 156560, 6)) - ); + AddAssert("selected in row", () => checkSnapAndSelectColumn(156560, new List<(int, int)> + { (156560, 1), (156560, 4), (156560, 6) } + )); AddStepClickLink("00:35:736 (35736|3,36395|3,36725|3,37384|3)"); - AddAssert("selected in column", () => - EditorClock.CurrentTime == 35736 - && EditorBeatmap.SelectedHitObjects.Count == 4 - && EditorBeatmap.SelectedHitObjects.All(x => - IsNoteAt(x, 35736, 3) || IsNoteAt(x, 36395, 3) || - IsNoteAt(x, 36725, 3) || IsNoteAt(x, 37384, 3)) - ); + AddAssert("selected in column", () => checkSnapAndSelectColumn(35736, new List<(int, int)> + { (35736, 3), (36395, 3), (36725, 3), (37384, 3) } + )); } [Test] @@ -355,23 +312,16 @@ namespace osu.Game.Tests.Visual.Editing SetUpEditor(rulesetInfo); AddAssert("is editor Mania", () => EditorBeatmap.BeatmapInfo.Ruleset.Equals(rulesetInfo)); - AddStepClickLink("00:00:000 (0|1)", "invalid link "); - AddAssert("snap to 1, select none", () => - EditorClock.CurrentTime == 956 - && !EditorBeatmap.SelectedHitObjects.Any() + AddStepClickLink("00:00:000 (0|1)", "invalid link"); + AddAssert("snap to 1, select none", () => checkSnapAndSelectColumn(956)); + + AddStepClickLink("00:00:000 (0)", "std link"); + AddAssert("snap and select 1", () => checkSnapAndSelectColumn(956, new List<(int, int)> + { (956, 1) }) ); - AddStepClickLink("00:00:000 (0)", "std link "); - AddAssert("snap and select 1", () => - EditorClock.CurrentTime == 956 - && EditorBeatmap.SelectedHitObjects.All(x => IsNoteAt(x, 956, 1)) - ); - - AddStepClickLink("00:00:000 (1,2)", "std link "); - AddAssert("snap to 1, select none", () => - EditorClock.CurrentTime == 956 - && !EditorBeatmap.SelectedHitObjects.Any() - ); + AddStepClickLink("00:00:000 (1,2)", "std link"); + AddAssert("snap to 1, select none", () => checkSnapAndSelectColumn(956)); } } } From 7492d953aee303732df79ba0267f34dbacfbb3e9 Mon Sep 17 00:00:00 2001 From: ratinfx Date: Sat, 4 Nov 2023 21:17:58 +0100 Subject: [PATCH 06/91] Moved error checks into Editor - Invoke Action on error to Notify user - added some comments --- .../Editing/TestSceneOpenEditorTimestamp.cs | 25 ++++++------ osu.Game/OsuGame.cs | 39 ++++--------------- osu.Game/Screens/Edit/Editor.cs | 27 ++++++++++++- .../Screens/Edit/EditorTimestampParser.cs | 4 +- 4 files changed, 47 insertions(+), 48 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneOpenEditorTimestamp.cs b/osu.Game.Tests/Visual/Editing/TestSceneOpenEditorTimestamp.cs index fea9334ff8..f65aff922e 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneOpenEditorTimestamp.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneOpenEditorTimestamp.cs @@ -67,17 +67,6 @@ namespace osu.Game.Tests.Visual.Editing && checkCombos; } - private bool checkSnapAndSelectColumn(double startTime, List<(int, int)> columnPairs = null) - { - bool checkColumns = columnPairs != null - ? EditorBeatmap.SelectedHitObjects.All(x => columnPairs.Any(col => isNoteAt(x, col.Item1, col.Item2))) - : !EditorBeatmap.SelectedHitObjects.Any(); - - return EditorClock.CurrentTime == startTime - && EditorBeatmap.SelectedHitObjects.Count == (columnPairs?.Count ?? 0) - && checkColumns; - } - private bool hasCombosInOrder(IEnumerable selected, params int[] comboNumbers) { List hitObjects = selected.ToList(); @@ -89,6 +78,17 @@ namespace osu.Game.Tests.Visual.Editing .Any(); } + private bool checkSnapAndSelectColumn(double startTime, IReadOnlyCollection<(int, int)> columnPairs = null) + { + bool checkColumns = columnPairs != null + ? EditorBeatmap.SelectedHitObjects.All(x => columnPairs.Any(col => isNoteAt(x, col.Item1, col.Item2))) + : !EditorBeatmap.SelectedHitObjects.Any(); + + return EditorClock.CurrentTime == startTime + && EditorBeatmap.SelectedHitObjects.Count == (columnPairs?.Count ?? 0) + && checkColumns; + } + private bool isNoteAt(HitObject hitObject, double time, int column) { return hitObject is IHasColumn columnInfo @@ -96,7 +96,7 @@ namespace osu.Game.Tests.Visual.Editing && columnInfo.Column == column; } - public void SetUpEditor(RulesetInfo ruleset) + protected void SetUpEditor(RulesetInfo ruleset) { BeatmapSetInfo beatmapSet = null!; @@ -320,6 +320,7 @@ namespace osu.Game.Tests.Visual.Editing { (956, 1) }) ); + // TODO: discuss - this selects the first 2 objects on Stable, do we want that or is this fine? AddStepClickLink("00:00:000 (1,2)", "std link"); AddAssert("snap to 1, select none", () => checkSnapAndSelectColumn(956)); } diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index a6bb6cc120..a9d4927e33 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -562,43 +562,18 @@ namespace osu.Game { if (ScreenStack.CurrentScreen is not Editor editor) { - waitForReady(() => Notifications, _ => Notifications.Post(new SimpleNotification - { - Icon = FontAwesome.Solid.ExclamationTriangle, - Text = EditorStrings.MustBeInEdit, - })); + postNotification(EditorStrings.MustBeInEdit); return; } - string[] groups = EditorTimestampParser.GetRegexGroups(timestamp); + editor.SeekAndSelectHitObjects(timestamp, onError: postNotification); + return; - if (groups.Length != 2 || string.IsNullOrEmpty(groups[0])) + void postNotification(LocalisableString message) => Schedule(() => Notifications.Post(new SimpleNotification { - waitForReady(() => Notifications, _ => Notifications.Post(new SimpleNotification - { - Icon = FontAwesome.Solid.ExclamationTriangle, - Text = EditorStrings.FailedToProcessTimestamp - })); - return; - } - - string timeGroup = groups[0]; - string objectsGroup = groups[1]; - string timeMinutes = timeGroup.Split(':').FirstOrDefault() ?? string.Empty; - - // Currently, lazer chat highlights infinite-long editor links like `10000000000:00:000 (1)` - // Limit timestamp link length at 30000 min (50 hr) to avoid parsing issues - if (timeMinutes.Length > 5 || double.Parse(timeMinutes) > 30_000) - { - waitForReady(() => Notifications, _ => Notifications.Post(new SimpleNotification - { - Icon = FontAwesome.Solid.ExclamationTriangle, - Text = EditorStrings.TooLongTimestamp - })); - return; - } - - editor.SeekAndSelectHitObjects(timeGroup, objectsGroup); + Icon = FontAwesome.Solid.ExclamationTriangle, + Text = message + })); } /// diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 80e01d4eb7..60d26d9ec0 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -1138,11 +1138,33 @@ namespace osu.Game.Screens.Edit loader?.CancelPendingDifficultySwitch(); } - public void SeekAndSelectHitObjects(string timeGroup, string objectsGroup) + public void SeekAndSelectHitObjects(string timestamp, Action onError) { + string[] groups = EditorTimestampParser.GetRegexGroups(timestamp); + + if (groups.Length != 2 || string.IsNullOrEmpty(groups[0])) + { + onError.Invoke(EditorStrings.FailedToProcessTimestamp); + return; + } + + string timeGroup = groups[0]; + string objectsGroup = groups[1]; + string timeMinutes = timeGroup.Split(':').FirstOrDefault() ?? string.Empty; + + // Currently, lazer chat highlights infinite-long editor links like `10000000000:00:000 (1)` + // Limit timestamp link length at 30000 min (50 hr) to avoid parsing issues + if (timeMinutes.Length > 5 || double.Parse(timeMinutes) > 30_000) + { + onError.Invoke(EditorStrings.TooLongTimestamp); + return; + } + double position = EditorTimestampParser.GetTotalMilliseconds(timeGroup); + editorBeatmap.SelectedHitObjects.Clear(); + // Only seeking is necessary if (string.IsNullOrEmpty(objectsGroup)) { if (clock.IsRunning) @@ -1155,8 +1177,9 @@ namespace osu.Game.Screens.Edit if (Mode.Value != EditorScreenMode.Compose) Mode.Value = EditorScreenMode.Compose; - // Seek to the next closest HitObject's position + // Seek to the next closest HitObject HitObject nextObject = editorBeatmap.HitObjects.FirstOrDefault(x => x.StartTime >= position); + if (nextObject != null && nextObject.StartTime > 0) position = nextObject.StartTime; diff --git a/osu.Game/Screens/Edit/EditorTimestampParser.cs b/osu.Game/Screens/Edit/EditorTimestampParser.cs index 44d614ca70..2d8f8a8f4c 100644 --- a/osu.Game/Screens/Edit/EditorTimestampParser.cs +++ b/osu.Game/Screens/Edit/EditorTimestampParser.cs @@ -32,7 +32,7 @@ namespace osu.Game.Screens.Edit return (times[0] * 60 + times[1]) * 1_000 + times[2]; } - public static List GetSelectedHitObjects(IEnumerable editorHitObjects, string objectsGroup, double position) + public static List GetSelectedHitObjects(IReadOnlyList editorHitObjects, string objectsGroup, double position) { List hitObjects = editorHitObjects.Where(x => x.StartTime >= position).ToList(); List selectedObjects = new List(); @@ -51,7 +51,7 @@ namespace osu.Game.Screens.Edit } // Stable behavior - // - always selects next closest object when `objectsGroup` only has one, non-Column item + // - always selects the next closest object when `objectsGroup` only has one (combo) item if (objectsToSelect.Length != 1 || objectsGroup.Contains('|')) return selectedObjects; From 277cf7dc127cfacaf62f51bc88e0a0a3c1474382 Mon Sep 17 00:00:00 2001 From: ratinfx Date: Sun, 5 Nov 2023 18:26:51 +0100 Subject: [PATCH 07/91] Ensure every SelectedItem is alive and has Blueprint --- .../Components/EditorBlueprintContainer.cs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/osu.Game/Screens/Edit/Compose/Components/EditorBlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/EditorBlueprintContainer.cs index ad0e8b124b..60959ca27a 100644 --- a/osu.Game/Screens/Edit/Compose/Components/EditorBlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/EditorBlueprintContainer.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; +using System.Collections.Specialized; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Graphics; @@ -51,6 +52,10 @@ namespace osu.Game.Screens.Edit.Compose.Components Beatmap.HitObjectAdded += AddBlueprintFor; Beatmap.HitObjectRemoved += RemoveBlueprintFor; + // This makes sure HitObjects will have active Blueprints ready to display + // after clicking on an Editor Timestamp/Link + Beatmap.SelectedHitObjects.CollectionChanged += SetHitObjectsAlive; + if (Composer != null) { foreach (var obj in Composer.HitObjects) @@ -144,6 +149,15 @@ namespace osu.Game.Screens.Edit.Compose.Components SelectedItems.AddRange(Beatmap.HitObjects.Except(SelectedItems).ToArray()); } + protected void SetHitObjectsAlive(object sender, NotifyCollectionChangedEventArgs e) + { + if (e == null || e.Action != NotifyCollectionChangedAction.Add || e.NewItems == null) + return; + + foreach (HitObject item in e.NewItems) + Composer.Playfield.SetKeepAlive(item, true); + } + protected override void OnBlueprintSelected(SelectionBlueprint blueprint) { base.OnBlueprintSelected(blueprint); @@ -166,6 +180,7 @@ namespace osu.Game.Screens.Edit.Compose.Components { Beatmap.HitObjectAdded -= AddBlueprintFor; Beatmap.HitObjectRemoved -= RemoveBlueprintFor; + Beatmap.SelectedHitObjects.CollectionChanged -= SetHitObjectsAlive; } usageEventBuffer?.Dispose(); From 8e8a88cfaf4852d4e80611677cb5948806096704 Mon Sep 17 00:00:00 2001 From: ratinfx Date: Tue, 7 Nov 2023 00:54:15 +0100 Subject: [PATCH 08/91] Removed nullable line from Test --- osu.Game.Tests/Visual/Editing/TestSceneOpenEditorTimestamp.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneOpenEditorTimestamp.cs b/osu.Game.Tests/Visual/Editing/TestSceneOpenEditorTimestamp.cs index f65aff922e..77dcbf069b 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneOpenEditorTimestamp.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneOpenEditorTimestamp.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System.Collections.Generic; using System.Linq; using NUnit.Framework; From 0834b79cc7fcb78873e381754f709deba0be4693 Mon Sep 17 00:00:00 2001 From: ratinfx Date: Tue, 7 Nov 2023 00:56:24 +0100 Subject: [PATCH 09/91] Renamed method and moved Notifications inside --- osu.Game/OsuGame.cs | 15 ++++++--------- osu.Game/Screens/Edit/Editor.cs | 16 ++++++++++++---- 2 files changed, 18 insertions(+), 13 deletions(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index a9d4927e33..be1776a330 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -562,18 +562,15 @@ namespace osu.Game { if (ScreenStack.CurrentScreen is not Editor editor) { - postNotification(EditorStrings.MustBeInEdit); + Schedule(() => Notifications.Post(new SimpleNotification + { + Icon = FontAwesome.Solid.ExclamationTriangle, + Text = EditorStrings.MustBeInEdit + })); return; } - editor.SeekAndSelectHitObjects(timestamp, onError: postNotification); - return; - - void postNotification(LocalisableString message) => Schedule(() => Notifications.Post(new SimpleNotification - { - Icon = FontAwesome.Solid.ExclamationTriangle, - Text = message - })); + editor.HandleTimestamp(timestamp); } /// diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 60d26d9ec0..9e0671e91d 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -1,4 +1,4 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. #nullable disable @@ -1138,13 +1138,17 @@ namespace osu.Game.Screens.Edit loader?.CancelPendingDifficultySwitch(); } - public void SeekAndSelectHitObjects(string timestamp, Action onError) + public void HandleTimestamp(string timestamp) { string[] groups = EditorTimestampParser.GetRegexGroups(timestamp); if (groups.Length != 2 || string.IsNullOrEmpty(groups[0])) { - onError.Invoke(EditorStrings.FailedToProcessTimestamp); + Schedule(() => notifications.Post(new SimpleNotification + { + Icon = FontAwesome.Solid.ExclamationTriangle, + Text = EditorStrings.FailedToProcessTimestamp + })); return; } @@ -1156,7 +1160,11 @@ namespace osu.Game.Screens.Edit // Limit timestamp link length at 30000 min (50 hr) to avoid parsing issues if (timeMinutes.Length > 5 || double.Parse(timeMinutes) > 30_000) { - onError.Invoke(EditorStrings.TooLongTimestamp); + Schedule(() => notifications.Post(new SimpleNotification + { + Icon = FontAwesome.Solid.ExclamationTriangle, + Text = EditorStrings.TooLongTimestamp + })); return; } From 44f127c8a86c9fa381118571c5af5e26f8cf141d Mon Sep 17 00:00:00 2001 From: ratinfx Date: Tue, 7 Nov 2023 01:02:45 +0100 Subject: [PATCH 10/91] Renamed method and made private --- .../Edit/Compose/Components/EditorBlueprintContainer.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/EditorBlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/EditorBlueprintContainer.cs index 60959ca27a..b68a690097 100644 --- a/osu.Game/Screens/Edit/Compose/Components/EditorBlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/EditorBlueprintContainer.cs @@ -54,7 +54,7 @@ namespace osu.Game.Screens.Edit.Compose.Components // This makes sure HitObjects will have active Blueprints ready to display // after clicking on an Editor Timestamp/Link - Beatmap.SelectedHitObjects.CollectionChanged += SetHitObjectsAlive; + Beatmap.SelectedHitObjects.CollectionChanged += keepHitObjectsAlive; if (Composer != null) { @@ -149,7 +149,7 @@ namespace osu.Game.Screens.Edit.Compose.Components SelectedItems.AddRange(Beatmap.HitObjects.Except(SelectedItems).ToArray()); } - protected void SetHitObjectsAlive(object sender, NotifyCollectionChangedEventArgs e) + private void keepHitObjectsAlive(object sender, NotifyCollectionChangedEventArgs e) { if (e == null || e.Action != NotifyCollectionChangedAction.Add || e.NewItems == null) return; @@ -180,7 +180,7 @@ namespace osu.Game.Screens.Edit.Compose.Components { Beatmap.HitObjectAdded -= AddBlueprintFor; Beatmap.HitObjectRemoved -= RemoveBlueprintFor; - Beatmap.SelectedHitObjects.CollectionChanged -= SetHitObjectsAlive; + Beatmap.SelectedHitObjects.CollectionChanged -= keepHitObjectsAlive; } usageEventBuffer?.Dispose(); From aa87e0a44d469bbdfb9abe9bca13ea0323eda774 Mon Sep 17 00:00:00 2001 From: ratinfx Date: Tue, 7 Nov 2023 01:36:58 +0100 Subject: [PATCH 11/91] HitObject Selection logic and separation for gamemodes + moved time_regex into EditorTimestampParser --- .../Edit/ManiaHitObjectComposer.cs | 19 ++++++- .../Edit/OsuHitObjectComposer.cs | 13 ++++- .../Editing/TestSceneOpenEditorTimestamp.cs | 30 ++++------- osu.Game/Online/Chat/MessageFormatter.cs | 7 +-- osu.Game/OsuGame.cs | 6 +-- .../Edit/EditorTimestampParser.cs | 54 +++++-------------- osu.Game/Rulesets/Edit/HitObjectComposer.cs | 13 +++++ osu.Game/Screens/Edit/Editor.cs | 50 ++++++++--------- 8 files changed, 95 insertions(+), 97 deletions(-) rename osu.Game/{Screens => Rulesets}/Edit/EditorTimestampParser.cs (50%) diff --git a/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs b/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs index b9db4168f4..d217f04651 100644 --- a/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs +++ b/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs @@ -3,6 +3,7 @@ #nullable disable +using System; using System.Collections.Generic; using System.Linq; using osu.Game.Beatmaps; @@ -11,6 +12,7 @@ using osu.Game.Rulesets.Edit.Tools; using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mania.UI; using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Screens.Edit.Compose.Components; @@ -49,6 +51,21 @@ namespace osu.Game.Rulesets.Mania.Edit }; public override string ConvertSelectionToString() - => string.Join(',', EditorBeatmap.SelectedHitObjects.Cast().OrderBy(h => h.StartTime).Select(h => $"{h.StartTime}|{h.Column}")); + => string.Join(ObjectSeparator, EditorBeatmap.SelectedHitObjects.Cast().OrderBy(h => h.StartTime).Select(h => $"{h.StartTime}|{h.Column}")); + + public override bool HandleHitObjectSelection(HitObject hitObject, string objectInfo) + { + if (hitObject is not ManiaHitObject maniaHitObject) + return false; + + double[] split = objectInfo.Split('|').Select(double.Parse).ToArray(); + if (split.Length != 2) + return false; + + double timeValue = split[0]; + double columnValue = split[1]; + return Math.Abs(maniaHitObject.StartTime - timeValue) < 0.5 + && Math.Abs(maniaHitObject.Column - columnValue) < 0.5; + } } } diff --git a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs index 0f8c960b65..0c63cf71d8 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs @@ -104,7 +104,18 @@ namespace osu.Game.Rulesets.Osu.Edit => new OsuBlueprintContainer(this); public override string ConvertSelectionToString() - => string.Join(',', selectedHitObjects.Cast().OrderBy(h => h.StartTime).Select(h => (h.IndexInCurrentCombo + 1).ToString())); + => string.Join(ObjectSeparator, selectedHitObjects.Cast().OrderBy(h => h.StartTime).Select(h => (h.IndexInCurrentCombo + 1).ToString())); + + public override bool HandleHitObjectSelection(HitObject hitObject, string objectInfo) + { + if (hitObject is not OsuHitObject osuHitObject) + return false; + + if (!int.TryParse(objectInfo, out int comboValue) || comboValue < 1) + return false; + + return osuHitObject.IndexInCurrentCombo + 1 == comboValue; + } private DistanceSnapGrid distanceSnapGrid; private Container distanceSnapGridContainer; diff --git a/osu.Game.Tests/Visual/Editing/TestSceneOpenEditorTimestamp.cs b/osu.Game.Tests/Visual/Editing/TestSceneOpenEditorTimestamp.cs index 77dcbf069b..f7b976702a 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneOpenEditorTimestamp.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneOpenEditorTimestamp.cs @@ -29,11 +29,14 @@ namespace osu.Game.Tests.Visual.Editing protected EditorBeatmap EditorBeatmap => Editor.ChildrenOfType().Single(); protected EditorClock EditorClock => Editor.ChildrenOfType().Single(); - protected void AddStepClickLink(string timestamp, string step = "") + protected void AddStepClickLink(string timestamp, string step = "", bool waitForSeek = true) { AddStep($"{step} {timestamp}", () => Game.HandleLink(new LinkDetails(LinkAction.OpenEditorTimestamp, timestamp)) ); + + if (waitForSeek) + AddUntilStep("wait for seek", () => EditorClock.SeekingOrStopped.Value); } protected void AddStepScreenModeTo(EditorScreenMode screenMode) @@ -76,7 +79,7 @@ namespace osu.Game.Tests.Visual.Editing .Any(); } - private bool checkSnapAndSelectColumn(double startTime, IReadOnlyCollection<(int, int)> columnPairs = null) + private bool checkSnapAndSelectColumn(double startTime, IReadOnlyCollection<(int, int)>? columnPairs = null) { bool checkColumns = columnPairs != null ? EditorBeatmap.SelectedHitObjects.All(x => columnPairs.Any(col => isNoteAt(x, col.Item1, col.Item2))) @@ -123,7 +126,7 @@ namespace osu.Game.Tests.Visual.Editing { RulesetInfo rulesetInfo = new OsuRuleset().RulesetInfo; - AddStepClickLink("00:00:000"); + AddStepClickLink("00:00:000", waitForSeek: false); AddAssert("recieved 'must be in edit'", () => Game.Notifications.AllNotifications.Count(x => x.Text == EditorStrings.MustBeInEdit) == 1 ); @@ -131,7 +134,7 @@ namespace osu.Game.Tests.Visual.Editing AddStep("enter song select", () => Game.ChildrenOfType().Single().OnSolo.Invoke()); AddAssert("entered song select", () => Game.ScreenStack.CurrentScreen is PlaySongSelect); - AddStepClickLink("00:00:000 (1)"); + AddStepClickLink("00:00:000 (1)", waitForSeek: false); AddAssert("recieved 'must be in edit'", () => Game.Notifications.AllNotifications.Count(x => x.Text == EditorStrings.MustBeInEdit) == 2 ); @@ -139,27 +142,12 @@ namespace osu.Game.Tests.Visual.Editing SetUpEditor(rulesetInfo); AddAssert("is editor Osu", () => EditorBeatmap.BeatmapInfo.Ruleset.Equals(rulesetInfo)); - AddStepClickLink("00:000", "invalid link"); + AddStepClickLink("00:000", "invalid link", waitForSeek: false); AddAssert("recieved 'failed to process'", () => Game.Notifications.AllNotifications.Count(x => x.Text == EditorStrings.FailedToProcessTimestamp) == 1 ); - AddStepClickLink("00:00:00:000", "invalid link"); - AddAssert("recieved 'failed to process'", () => - Game.Notifications.AllNotifications.Count(x => x.Text == EditorStrings.FailedToProcessTimestamp) == 2 - ); - - AddStepClickLink("00:00:000 ()", "invalid link"); - AddAssert("recieved 'failed to process'", () => - Game.Notifications.AllNotifications.Count(x => x.Text == EditorStrings.FailedToProcessTimestamp) == 3 - ); - - AddStepClickLink("00:00:000 (-1)", "invalid link"); - AddAssert("recieved 'failed to process'", () => - Game.Notifications.AllNotifications.Count(x => x.Text == EditorStrings.FailedToProcessTimestamp) == 4 - ); - - AddStepClickLink("50000:00:000", "too long link"); + AddStepClickLink("50000:00:000", "too long link", waitForSeek: false); AddAssert("recieved 'too long'", () => Game.Notifications.AllNotifications.Count(x => x.Text == EditorStrings.TooLongTimestamp) == 1 ); diff --git a/osu.Game/Online/Chat/MessageFormatter.cs b/osu.Game/Online/Chat/MessageFormatter.cs index 667175117f..9a194dba47 100644 --- a/osu.Game/Online/Chat/MessageFormatter.cs +++ b/osu.Game/Online/Chat/MessageFormatter.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.Linq; using System.Text.RegularExpressions; using osu.Game.Online.API.Requests.Responses; +using osu.Game.Rulesets.Edit; namespace osu.Game.Online.Chat { @@ -41,10 +42,6 @@ namespace osu.Game.Online.Chat @"(?:#(?:[a-z0-9$_\+!\*\',;:\(\)@&=\/~-]|%[0-9a-f]{2})*)?)?)", RegexOptions.IgnoreCase); - // 00:00:000 (1,2,3) - test - // regex from https://github.com/ppy/osu-web/blob/651a9bac2b60d031edd7e33b8073a469bf11edaa/resources/assets/coffee/_classes/beatmap-discussion-helper.coffee#L10 - private static readonly Regex time_regex = new Regex(@"\b(((\d{2,}):([0-5]\d)[:.](\d{3}))(\s\((?:\d+[,|])*\d+\))?)"); - // #osu private static readonly Regex channel_regex = new Regex(@"(#[a-zA-Z]+[a-zA-Z0-9]+)"); @@ -274,7 +271,7 @@ namespace osu.Game.Online.Chat handleAdvanced(advanced_link_regex, result, startIndex); // handle editor times - handleMatches(time_regex, "{0}", $@"{OsuGameBase.OSU_PROTOCOL}edit/{{0}}", result, startIndex, LinkAction.OpenEditorTimestamp); + handleMatches(EditorTimestampParser.TIME_REGEX, "{0}", $@"{OsuGameBase.OSU_PROTOCOL}edit/{{0}}", result, startIndex, LinkAction.OpenEditorTimestamp); // handle channels handleMatches(channel_regex, "{0}", $@"{OsuGameBase.OSU_PROTOCOL}chan/{{0}}", result, startIndex, LinkAction.OpenChannel); diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index be1776a330..cde8ee1457 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -563,10 +563,10 @@ namespace osu.Game if (ScreenStack.CurrentScreen is not Editor editor) { Schedule(() => Notifications.Post(new SimpleNotification - { - Icon = FontAwesome.Solid.ExclamationTriangle, + { + Icon = FontAwesome.Solid.ExclamationTriangle, Text = EditorStrings.MustBeInEdit - })); + })); return; } diff --git a/osu.Game/Screens/Edit/EditorTimestampParser.cs b/osu.Game/Rulesets/Edit/EditorTimestampParser.cs similarity index 50% rename from osu.Game/Screens/Edit/EditorTimestampParser.cs rename to osu.Game/Rulesets/Edit/EditorTimestampParser.cs index 2d8f8a8f4c..4e5a696102 100644 --- a/osu.Game/Screens/Edit/EditorTimestampParser.cs +++ b/osu.Game/Rulesets/Edit/EditorTimestampParser.cs @@ -7,41 +7,43 @@ using System.Diagnostics; using System.Linq; using System.Text.RegularExpressions; using osu.Game.Rulesets.Objects; -using osu.Game.Rulesets.Objects.Types; -namespace osu.Game.Screens.Edit +namespace osu.Game.Rulesets.Edit { public static class EditorTimestampParser { - private static readonly Regex timestamp_regex = new Regex(@"^(\d+:\d+:\d+)(?: \((\d+(?:[|,]\d+)*)\))?$", RegexOptions.Compiled); + // 00:00:000 (1,2,3) - test + // regex from https://github.com/ppy/osu-web/blob/651a9bac2b60d031edd7e33b8073a469bf11edaa/resources/assets/coffee/_classes/beatmap-discussion-helper.coffee#L10 + public static readonly Regex TIME_REGEX = new Regex(@"\b(((\d{2,}):([0-5]\d)[:.](\d{3}))(\s\((?:\d+[,|])*\d+\))?)"); public static string[] GetRegexGroups(string timestamp) { - Match match = timestamp_regex.Match(timestamp); - return match.Success - ? match.Groups.Values.Where(x => x is not Match).Select(x => x.Value).ToArray() + Match match = TIME_REGEX.Match(timestamp); + string[] result = match.Success + ? match.Groups.Values.Where(x => x is not Match && !x.Value.Contains(':')).Select(x => x.Value).ToArray() : Array.Empty(); + return result; } - public static double GetTotalMilliseconds(string timeGroup) + public static double GetTotalMilliseconds(params string[] timesGroup) { - int[] times = timeGroup.Split(':').Select(int.Parse).ToArray(); + int[] times = timesGroup.Select(int.Parse).ToArray(); Debug.Assert(times.Length == 3); return (times[0] * 60 + times[1]) * 1_000 + times[2]; } - public static List GetSelectedHitObjects(IReadOnlyList editorHitObjects, string objectsGroup, double position) + public static List GetSelectedHitObjects(HitObjectComposer composer, IReadOnlyList editorHitObjects, string objectsGroup, double position) { List hitObjects = editorHitObjects.Where(x => x.StartTime >= position).ToList(); List selectedObjects = new List(); - string[] objectsToSelect = objectsGroup.Split(',').ToArray(); + string[] objectsToSelect = objectsGroup.Split(composer.ObjectSeparator).ToArray(); foreach (string objectInfo in objectsToSelect) { - HitObject? current = hitObjects.FirstOrDefault(x => shouldHitObjectBeSelected(x, objectInfo)); + HitObject? current = hitObjects.FirstOrDefault(x => composer.HandleHitObjectSelection(x, objectInfo)); if (current == null) continue; @@ -67,35 +69,5 @@ namespace osu.Game.Screens.Edit return selectedObjects; } - - private static bool shouldHitObjectBeSelected(HitObject hitObject, string objectInfo) - { - switch (hitObject) - { - // (combo) - case IHasComboInformation comboInfo: - { - if (!double.TryParse(objectInfo, out double comboValue) || comboValue < 1) - return false; - - return comboInfo.IndexInCurrentCombo + 1 == comboValue; - } - - // (time|column) - case IHasColumn column: - { - double[] split = objectInfo.Split('|').Select(double.Parse).ToArray(); - if (split.Length != 2) - return false; - - double timeValue = split[0]; - double columnValue = split[1]; - return hitObject.StartTime == timeValue && column.Column == columnValue; - } - - default: - return false; - } - } } } diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index 07e5869e28..f6cddcc0d2 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -528,6 +528,19 @@ namespace osu.Game.Rulesets.Edit public virtual string ConvertSelectionToString() => string.Empty; + /// + /// The custom logic that decides whether a HitObject should be selected when clicking an editor timestamp link + /// + /// The hitObject being checked + /// A single hitObject's information created with + /// Whether a HitObject should be selected or not + public virtual bool HandleHitObjectSelection(HitObject hitObject, string objectInfo) => false; + + /// + /// A character that separates the selection in + /// + public virtual char ObjectSeparator => ','; + #region IPositionSnapProvider public abstract SnapResult FindSnappedPositionAndTime(Vector2 screenSpacePosition, SnapType snapType = SnapType.All); diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 9e0671e91d..592e6625cc 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -1,4 +1,4 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. #nullable disable @@ -14,6 +14,7 @@ using osu.Framework.Audio.Track; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.UserInterface; using osu.Framework.Input; using osu.Framework.Input.Bindings; @@ -1142,7 +1143,7 @@ namespace osu.Game.Screens.Edit { string[] groups = EditorTimestampParser.GetRegexGroups(timestamp); - if (groups.Length != 2 || string.IsNullOrEmpty(groups[0])) + if (groups.Length != 4 || string.IsNullOrEmpty(groups[0])) { Schedule(() => notifications.Post(new SimpleNotification { @@ -1152,13 +1153,14 @@ namespace osu.Game.Screens.Edit return; } - string timeGroup = groups[0]; - string objectsGroup = groups[1]; - string timeMinutes = timeGroup.Split(':').FirstOrDefault() ?? string.Empty; + string timeMin = groups[0]; + string timeSec = groups[1]; + string timeMss = groups[2]; + string objectsGroup = groups[3].Replace("(", "").Replace(")", "").Trim(); // Currently, lazer chat highlights infinite-long editor links like `10000000000:00:000 (1)` // Limit timestamp link length at 30000 min (50 hr) to avoid parsing issues - if (timeMinutes.Length > 5 || double.Parse(timeMinutes) > 30_000) + if (string.IsNullOrEmpty(timeMin) || timeMin.Length > 5 || double.Parse(timeMin) > 30_000) { Schedule(() => notifications.Post(new SimpleNotification { @@ -1168,38 +1170,36 @@ namespace osu.Game.Screens.Edit return; } - double position = EditorTimestampParser.GetTotalMilliseconds(timeGroup); - editorBeatmap.SelectedHitObjects.Clear(); - // Only seeking is necessary + double position = EditorTimestampParser.GetTotalMilliseconds(timeMin, timeSec, timeMss); + if (string.IsNullOrEmpty(objectsGroup)) { - if (clock.IsRunning) - clock.Stop(); - - clock.Seek(position); + clock.SeekSmoothlyTo(position); return; } + // Seek to the next closest HitObject instead + HitObject nextObject = editorBeatmap.HitObjects.FirstOrDefault(x => x.StartTime >= position); + + if (nextObject != null) + position = nextObject.StartTime; + + clock.SeekSmoothlyTo(position); + if (Mode.Value != EditorScreenMode.Compose) Mode.Value = EditorScreenMode.Compose; - // Seek to the next closest HitObject - HitObject nextObject = editorBeatmap.HitObjects.FirstOrDefault(x => x.StartTime >= position); - - if (nextObject != null && nextObject.StartTime > 0) - position = nextObject.StartTime; - - List selected = EditorTimestampParser.GetSelectedHitObjects(editorBeatmap.HitObjects.ToList(), objectsGroup, position); + List selected = EditorTimestampParser.GetSelectedHitObjects( + currentScreen.Dependencies.Get(), + editorBeatmap.HitObjects.ToList(), + objectsGroup, + position + ); if (selected.Any()) editorBeatmap.SelectedHitObjects.AddRange(selected); - - if (clock.IsRunning) - clock.Stop(); - - clock.Seek(position); } public double SnapTime(double time, double? referenceTime) => editorBeatmap.SnapTime(time, referenceTime); From bdbeb2bce4286122584e0c7f9dff61bdc5d57ca1 Mon Sep 17 00:00:00 2001 From: ratinfx Date: Tue, 7 Nov 2023 11:11:32 +0100 Subject: [PATCH 12/91] Renamed CollectionChanged event handler --- .../Edit/Compose/Components/EditorBlueprintContainer.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/EditorBlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/EditorBlueprintContainer.cs index b68a690097..a311054ffc 100644 --- a/osu.Game/Screens/Edit/Compose/Components/EditorBlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/EditorBlueprintContainer.cs @@ -54,7 +54,7 @@ namespace osu.Game.Screens.Edit.Compose.Components // This makes sure HitObjects will have active Blueprints ready to display // after clicking on an Editor Timestamp/Link - Beatmap.SelectedHitObjects.CollectionChanged += keepHitObjectsAlive; + Beatmap.SelectedHitObjects.CollectionChanged += selectionChanged; if (Composer != null) { @@ -149,7 +149,7 @@ namespace osu.Game.Screens.Edit.Compose.Components SelectedItems.AddRange(Beatmap.HitObjects.Except(SelectedItems).ToArray()); } - private void keepHitObjectsAlive(object sender, NotifyCollectionChangedEventArgs e) + private void selectionChanged(object sender, NotifyCollectionChangedEventArgs e) { if (e == null || e.Action != NotifyCollectionChangedAction.Add || e.NewItems == null) return; @@ -180,7 +180,7 @@ namespace osu.Game.Screens.Edit.Compose.Components { Beatmap.HitObjectAdded -= AddBlueprintFor; Beatmap.HitObjectRemoved -= RemoveBlueprintFor; - Beatmap.SelectedHitObjects.CollectionChanged -= keepHitObjectsAlive; + Beatmap.SelectedHitObjects.CollectionChanged -= selectionChanged; } usageEventBuffer?.Dispose(); From 544d5d1d86416a179de9c6badd7833a8e53ca518 Mon Sep 17 00:00:00 2001 From: ratinfx Date: Tue, 7 Nov 2023 12:23:22 +0100 Subject: [PATCH 13/91] Forgot a clock.Stop call --- osu.Game/Screens/Edit/Editor.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 592e6625cc..58c3ae809c 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -1145,7 +1145,7 @@ namespace osu.Game.Screens.Edit if (groups.Length != 4 || string.IsNullOrEmpty(groups[0])) { - Schedule(() => notifications.Post(new SimpleNotification + Schedule(() => notifications?.Post(new SimpleNotification { Icon = FontAwesome.Solid.ExclamationTriangle, Text = EditorStrings.FailedToProcessTimestamp @@ -1162,7 +1162,7 @@ namespace osu.Game.Screens.Edit // Limit timestamp link length at 30000 min (50 hr) to avoid parsing issues if (string.IsNullOrEmpty(timeMin) || timeMin.Length > 5 || double.Parse(timeMin) > 30_000) { - Schedule(() => notifications.Post(new SimpleNotification + Schedule(() => notifications?.Post(new SimpleNotification { Icon = FontAwesome.Solid.ExclamationTriangle, Text = EditorStrings.TooLongTimestamp @@ -1172,6 +1172,9 @@ namespace osu.Game.Screens.Edit editorBeatmap.SelectedHitObjects.Clear(); + if (clock.IsRunning) + clock.Stop(); + double position = EditorTimestampParser.GetTotalMilliseconds(timeMin, timeSec, timeMss); if (string.IsNullOrEmpty(objectsGroup)) From 81caa854e6a7ba3251658f1cd174bf6bad62b1ea Mon Sep 17 00:00:00 2001 From: ratinfx Date: Tue, 7 Nov 2023 13:02:46 +0100 Subject: [PATCH 14/91] Separate Test cases by relevant rulesets --- .../TestSceneOpenEditorTimestampInMania.cs | 102 +++++++ .../TestSceneOpenEditorTimestampInOsu.cs | 110 ++++++++ .../Editing/TestSceneOpenEditorTimestamp.cs | 249 ++++-------------- 3 files changed, 259 insertions(+), 202 deletions(-) create mode 100644 osu.Game.Rulesets.Mania.Tests/Editor/TestSceneOpenEditorTimestampInMania.cs create mode 100644 osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOpenEditorTimestampInOsu.cs diff --git a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneOpenEditorTimestampInMania.cs b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneOpenEditorTimestampInMania.cs new file mode 100644 index 0000000000..6ec5dcee4c --- /dev/null +++ b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneOpenEditorTimestampInMania.cs @@ -0,0 +1,102 @@ +// 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.Rulesets.Mania.Objects; +using osu.Game.Rulesets.Objects; +using osu.Game.Tests.Visual; + +namespace osu.Game.Rulesets.Mania.Tests.Editor +{ + public partial class TestSceneOpenEditorTimestampInMania : EditorTestScene + { + protected override Ruleset CreateEditorRuleset() => new ManiaRuleset(); + + private void addStepClickLink(string timestamp, string step = "", bool displayTimestamp = true) + { + AddStep(displayTimestamp ? $"{step} {timestamp}" : step, () => Editor.HandleTimestamp(timestamp)); + AddUntilStep("wait for seek", () => EditorClock.SeekingOrStopped.Value); + } + + private void addReset() + { + addStepClickLink("00:00:000", "reset", false); + } + + private bool checkSnapAndSelectColumn(double startTime, IReadOnlyCollection<(int, int)>? columnPairs = null) + { + bool checkColumns = columnPairs != null + ? EditorBeatmap.SelectedHitObjects.All(x => columnPairs.Any(col => isNoteAt(x, col.Item1, col.Item2))) + : !EditorBeatmap.SelectedHitObjects.Any(); + + return EditorClock.CurrentTime == startTime + && EditorBeatmap.SelectedHitObjects.Count == (columnPairs?.Count ?? 0) + && checkColumns; + } + + private bool isNoteAt(HitObject hitObject, double time, int column) + { + return hitObject is ManiaHitObject maniaHitObject + && maniaHitObject.StartTime == time + && maniaHitObject.Column == column; + } + + [Test] + public void TestNormalSelection() + { + addStepClickLink("00:05:920 (5920|3,6623|3,6857|2,7326|1)"); + AddAssert("selected group", () => checkSnapAndSelectColumn(5_920, new List<(int, int)> + { (5_920, 3), (6_623, 3), (6_857, 2), (7_326, 1) } + )); + + addReset(); + addStepClickLink("00:42:716 (42716|3,43420|2,44123|0,44357|1,45295|1)"); + AddAssert("selected ungrouped", () => checkSnapAndSelectColumn(42_716, new List<(int, int)> + { (42_716, 3), (43_420, 2), (44_123, 0), (44_357, 1), (45_295, 1) } + )); + + addReset(); + AddStep("add notes to row", () => + { + if (EditorBeatmap.HitObjects.Any(x => x is ManiaHitObject m && m.StartTime == 11_545 && m.Column is 1 or 2 or 3)) + return; + + ManiaHitObject first = (ManiaHitObject)EditorBeatmap.HitObjects.First(x => x is ManiaHitObject m && m.StartTime == 11_545 && m.Column == 0); + ManiaHitObject second = new Note { Column = 1, StartTime = first.StartTime }; + ManiaHitObject third = new Note { Column = 2, StartTime = first.StartTime }; + ManiaHitObject forth = new Note { Column = 3, StartTime = first.StartTime }; + EditorBeatmap.AddRange(new[] { second, third, forth }); + }); + addStepClickLink("00:11:545 (11545|0,11545|1,11545|2,11545|3)"); + AddAssert("selected in row", () => checkSnapAndSelectColumn(11_545, new List<(int, int)> + { (11_545, 0), (11_545, 1), (11_545, 2), (11_545, 3) } + )); + + addReset(); + addStepClickLink("01:36:623 (96623|1,97560|1,97677|1,97795|1,98966|1)"); + AddAssert("selected in column", () => checkSnapAndSelectColumn(96_623, new List<(int, int)> + { (96_623, 1), (97_560, 1), (97_677, 1), (97_795, 1), (98_966, 1) } + )); + } + + [Test] + public void TestUnusualSelection() + { + addStepClickLink("00:00:000 (0|1)", "invalid link"); + AddAssert("snap to 1, select none", () => checkSnapAndSelectColumn(2_170)); + + addReset(); + addStepClickLink("00:00:000 (0)", "std link"); + AddAssert("snap and select 1", () => checkSnapAndSelectColumn(2_170, new List<(int, int)> + { (2_170, 2) }) + ); + + addReset(); + // TODO: discuss - this selects the first 2 objects on Stable, do we want that or is this fine? + addStepClickLink("00:00:000 (1,2)", "std link"); + AddAssert("snap to 1, select none", () => checkSnapAndSelectColumn(2_170)); + } + } +} diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOpenEditorTimestampInOsu.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOpenEditorTimestampInOsu.cs new file mode 100644 index 0000000000..d69f482d29 --- /dev/null +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOpenEditorTimestampInOsu.cs @@ -0,0 +1,110 @@ +// 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.Rulesets.Objects; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Tests.Visual; + +namespace osu.Game.Rulesets.Osu.Tests.Editor +{ + public partial class TestSceneOpenEditorTimestampInOsu : EditorTestScene + { + protected override Ruleset CreateEditorRuleset() => new OsuRuleset(); + + private void addStepClickLink(string timestamp, string step = "", bool displayTimestamp = true) + { + AddStep(displayTimestamp ? $"{step} {timestamp}" : step, () => Editor.HandleTimestamp(timestamp)); + AddUntilStep("wait for seek", () => EditorClock.SeekingOrStopped.Value); + } + + private void addReset() + { + addStepClickLink("00:00:000", "reset", false); + } + + private bool checkSnapAndSelectCombo(double startTime, params int[] comboNumbers) + { + bool checkCombos = comboNumbers.Any() + ? hasCombosInOrder(EditorBeatmap.SelectedHitObjects, comboNumbers) + : !EditorBeatmap.SelectedHitObjects.Any(); + + return EditorClock.CurrentTime == startTime + && EditorBeatmap.SelectedHitObjects.Count == comboNumbers.Length + && checkCombos; + } + + private bool hasCombosInOrder(IEnumerable selected, params int[] comboNumbers) + { + List hitObjects = selected.ToList(); + if (hitObjects.Count != comboNumbers.Length) + return false; + + return !hitObjects.Select(x => (OsuHitObject)x) + .Where((x, i) => x.IndexInCurrentCombo + 1 != comboNumbers[i]) + .Any(); + } + + [Test] + public void TestNormalSelection() + { + addStepClickLink("00:02:170 (1,2,3)"); + AddAssert("snap and select 1-2-3", () => checkSnapAndSelectCombo(2_170, 1, 2, 3)); + + addReset(); + addStepClickLink("00:04:748 (2,3,4,1,2)"); + AddAssert("snap and select 2-3-4-1-2", () => checkSnapAndSelectCombo(4_748, 2, 3, 4, 1, 2)); + + addReset(); + addStepClickLink("00:02:170 (1,1,1)"); + AddAssert("snap and select 1-1-1", () => checkSnapAndSelectCombo(2_170, 1, 1, 1)); + + addReset(); + addStepClickLink("00:02:873 (2,2,2,2)"); + AddAssert("snap and select 2-2-2-2", () => checkSnapAndSelectCombo(2_873, 2, 2, 2, 2)); + } + + [Test] + public void TestUnusualSelection() + { + HitObject firstObject = null!; + + addStepClickLink("00:00:000 (1,2,3)", "invalid offset"); + AddAssert("snap to next, select 1-2-3", () => + { + firstObject = EditorBeatmap.HitObjects.First(); + return checkSnapAndSelectCombo(firstObject.StartTime, 1, 2, 3); + }); + + addReset(); + addStepClickLink("00:00:956 (2,3,4)", "invalid offset"); + AddAssert("snap to next, select 2-3-4", () => checkSnapAndSelectCombo(firstObject.StartTime, 2, 3, 4)); + + addReset(); + addStepClickLink("00:00:000 (0)", "invalid offset"); + AddAssert("snap and select 1", () => checkSnapAndSelectCombo(firstObject.StartTime, 1)); + + addReset(); + addStepClickLink("00:00:000 (1)", "invalid offset"); + AddAssert("snap and select 1", () => checkSnapAndSelectCombo(firstObject.StartTime, 1)); + + addReset(); + addStepClickLink("00:00:000 (2)", "invalid offset"); + AddAssert("snap and select 1", () => checkSnapAndSelectCombo(firstObject.StartTime, 1)); + + addReset(); + addStepClickLink("00:00:000 (2,3)", "invalid offset"); + AddAssert("snap to 1, select 2-3", () => checkSnapAndSelectCombo(firstObject.StartTime, 2, 3)); + + addReset(); + addStepClickLink("00:00:956 (956|1,956|2)", "mania link"); + AddAssert("snap to next, select none", () => checkSnapAndSelectCombo(firstObject.StartTime)); + + addReset(); + addStepClickLink("00:00:000 (0|1)", "mania link"); + AddAssert("snap to 1, select none", () => checkSnapAndSelectCombo(firstObject.StartTime)); + } + } +} diff --git a/osu.Game.Tests/Visual/Editing/TestSceneOpenEditorTimestamp.cs b/osu.Game.Tests/Visual/Editing/TestSceneOpenEditorTimestamp.cs index f7b976702a..bc31924e2c 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneOpenEditorTimestamp.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneOpenEditorTimestamp.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.Framework.Extensions; @@ -12,9 +11,6 @@ using osu.Game.Database; using osu.Game.Localisation; using osu.Game.Online.Chat; using osu.Game.Rulesets; -using osu.Game.Rulesets.Mania; -using osu.Game.Rulesets.Objects; -using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Osu; using osu.Game.Screens.Edit; using osu.Game.Screens.Menu; @@ -25,79 +21,39 @@ namespace osu.Game.Tests.Visual.Editing { public partial class TestSceneOpenEditorTimestamp : OsuGameTestScene { - protected Editor Editor => (Editor)Game.ScreenStack.CurrentScreen; - protected EditorBeatmap EditorBeatmap => Editor.ChildrenOfType().Single(); - protected EditorClock EditorClock => Editor.ChildrenOfType().Single(); + private Editor editor => (Editor)Game.ScreenStack.CurrentScreen; + private EditorBeatmap editorBeatmap => editor.ChildrenOfType().Single(); + private EditorClock editorClock => editor.ChildrenOfType().Single(); - protected void AddStepClickLink(string timestamp, string step = "", bool waitForSeek = true) + private void addStepClickLink(string timestamp, string step = "", bool waitForSeek = true) { AddStep($"{step} {timestamp}", () => Game.HandleLink(new LinkDetails(LinkAction.OpenEditorTimestamp, timestamp)) ); if (waitForSeek) - AddUntilStep("wait for seek", () => EditorClock.SeekingOrStopped.Value); + AddUntilStep("wait for seek", () => editorClock.SeekingOrStopped.Value); } - protected void AddStepScreenModeTo(EditorScreenMode screenMode) + private void addStepScreenModeTo(EditorScreenMode screenMode) { - AddStep("change screen to " + screenMode, () => Editor.Mode.Value = screenMode); + AddStep("change screen to " + screenMode, () => editor.Mode.Value = screenMode); } - protected void AssertOnScreenAt(EditorScreenMode screen, double time, string text = "stayed in") + private void assertOnScreenAt(EditorScreenMode screen, double time, string text = "stayed in") { AddAssert($"{text} {screen} at {time}", () => - Editor.Mode.Value == screen - && EditorClock.CurrentTime == time + editor.Mode.Value == screen + && editorClock.CurrentTime == time ); } - protected void AssertMovedScreenTo(EditorScreenMode screen, string text = "moved to") + private void assertMovedScreenTo(EditorScreenMode screen, string text = "moved to") { - AddAssert($"{text} {screen}", () => Editor.Mode.Value == screen); + AddAssert($"{text} {screen}", () => editor.Mode.Value == screen); } - private bool checkSnapAndSelectCombo(double startTime, params int[] comboNumbers) - { - bool checkCombos = comboNumbers.Any() - ? hasCombosInOrder(EditorBeatmap.SelectedHitObjects, comboNumbers) - : !EditorBeatmap.SelectedHitObjects.Any(); - - return EditorClock.CurrentTime == startTime - && EditorBeatmap.SelectedHitObjects.Count == comboNumbers.Length - && checkCombos; - } - - private bool hasCombosInOrder(IEnumerable selected, params int[] comboNumbers) - { - List hitObjects = selected.ToList(); - if (hitObjects.Count != comboNumbers.Length) - return false; - - return !hitObjects.Select(x => (IHasComboInformation)x) - .Where((combo, i) => combo.IndexInCurrentCombo + 1 != comboNumbers[i]) - .Any(); - } - - private bool checkSnapAndSelectColumn(double startTime, IReadOnlyCollection<(int, int)>? columnPairs = null) - { - bool checkColumns = columnPairs != null - ? EditorBeatmap.SelectedHitObjects.All(x => columnPairs.Any(col => isNoteAt(x, col.Item1, col.Item2))) - : !EditorBeatmap.SelectedHitObjects.Any(); - - return EditorClock.CurrentTime == startTime - && EditorBeatmap.SelectedHitObjects.Count == (columnPairs?.Count ?? 0) - && checkColumns; - } - - private bool isNoteAt(HitObject hitObject, double time, int column) - { - return hitObject is IHasColumn columnInfo - && hitObject.StartTime == time - && columnInfo.Column == column; - } - - protected void SetUpEditor(RulesetInfo ruleset) + private void setUpEditor(RulesetInfo ruleset) { BeatmapSetInfo beatmapSet = null!; @@ -118,7 +74,7 @@ namespace osu.Game.Tests.Visual.Editing ((PlaySongSelect)Game.ScreenStack.CurrentScreen) .Edit(beatmapSet.Beatmaps.Last(beatmap => beatmap.Ruleset.Name == ruleset.Name)) ); - AddUntilStep("Wait for editor open", () => Editor.ReadyForUse); + AddUntilStep("Wait for editor open", () => editor.ReadyForUse); } [Test] @@ -126,7 +82,7 @@ namespace osu.Game.Tests.Visual.Editing { RulesetInfo rulesetInfo = new OsuRuleset().RulesetInfo; - AddStepClickLink("00:00:000", waitForSeek: false); + addStepClickLink("00:00:000", waitForSeek: false); AddAssert("recieved 'must be in edit'", () => Game.Notifications.AllNotifications.Count(x => x.Text == EditorStrings.MustBeInEdit) == 1 ); @@ -134,20 +90,20 @@ namespace osu.Game.Tests.Visual.Editing AddStep("enter song select", () => Game.ChildrenOfType().Single().OnSolo.Invoke()); AddAssert("entered song select", () => Game.ScreenStack.CurrentScreen is PlaySongSelect); - AddStepClickLink("00:00:000 (1)", waitForSeek: false); + addStepClickLink("00:00:000 (1)", waitForSeek: false); AddAssert("recieved 'must be in edit'", () => Game.Notifications.AllNotifications.Count(x => x.Text == EditorStrings.MustBeInEdit) == 2 ); - SetUpEditor(rulesetInfo); - AddAssert("is editor Osu", () => EditorBeatmap.BeatmapInfo.Ruleset.Equals(rulesetInfo)); + setUpEditor(rulesetInfo); + AddAssert("is editor Osu", () => editorBeatmap.BeatmapInfo.Ruleset.Equals(rulesetInfo)); - AddStepClickLink("00:000", "invalid link", waitForSeek: false); + addStepClickLink("00:000", "invalid link", waitForSeek: false); AddAssert("recieved 'failed to process'", () => Game.Notifications.AllNotifications.Count(x => x.Text == EditorStrings.FailedToProcessTimestamp) == 1 ); - AddStepClickLink("50000:00:000", "too long link", waitForSeek: false); + addStepClickLink("50000:00:000", "too long link", waitForSeek: false); AddAssert("recieved 'too long'", () => Game.Notifications.AllNotifications.Count(x => x.Text == EditorStrings.TooLongTimestamp) == 1 ); @@ -159,156 +115,45 @@ namespace osu.Game.Tests.Visual.Editing const long long_link_value = 1_000 * 60 * 1_000; RulesetInfo rulesetInfo = new OsuRuleset().RulesetInfo; - SetUpEditor(rulesetInfo); - AddAssert("is editor Osu", () => EditorBeatmap.BeatmapInfo.Ruleset.Equals(rulesetInfo)); + setUpEditor(rulesetInfo); + AddAssert("is editor Osu", () => editorBeatmap.BeatmapInfo.Ruleset.Equals(rulesetInfo)); - AddStepClickLink("1000:00:000", "long link"); + addStepClickLink("1000:00:000", "long link"); AddAssert("moved to end of track", () => - EditorClock.CurrentTime == long_link_value - || (EditorClock.TrackLength < long_link_value && EditorClock.CurrentTime == EditorClock.TrackLength) + editorClock.CurrentTime == long_link_value + || (editorClock.TrackLength < long_link_value && editorClock.CurrentTime == editorClock.TrackLength) ); - AddStepScreenModeTo(EditorScreenMode.SongSetup); - AddStepClickLink("00:00:000"); - AssertOnScreenAt(EditorScreenMode.SongSetup, 0); + addStepScreenModeTo(EditorScreenMode.SongSetup); + addStepClickLink("00:00:000"); + assertOnScreenAt(EditorScreenMode.SongSetup, 0); - AddStepClickLink("00:05:000 (0|0)"); - AssertMovedScreenTo(EditorScreenMode.Compose); + addStepClickLink("00:05:000 (0|0)"); + assertMovedScreenTo(EditorScreenMode.Compose); - AddStepScreenModeTo(EditorScreenMode.Design); - AddStepClickLink("00:10:000"); - AssertOnScreenAt(EditorScreenMode.Design, 10_000); + addStepScreenModeTo(EditorScreenMode.Design); + addStepClickLink("00:10:000"); + assertOnScreenAt(EditorScreenMode.Design, 10_000); - AddStepClickLink("00:15:000 (1)"); - AssertMovedScreenTo(EditorScreenMode.Compose); + addStepClickLink("00:15:000 (1)"); + assertMovedScreenTo(EditorScreenMode.Compose); - AddStepScreenModeTo(EditorScreenMode.Timing); - AddStepClickLink("00:20:000"); - AssertOnScreenAt(EditorScreenMode.Timing, 20_000); + addStepScreenModeTo(EditorScreenMode.Timing); + addStepClickLink("00:20:000"); + assertOnScreenAt(EditorScreenMode.Timing, 20_000); - AddStepClickLink("00:25:000 (0,1)"); - AssertMovedScreenTo(EditorScreenMode.Compose); + addStepClickLink("00:25:000 (0,1)"); + assertMovedScreenTo(EditorScreenMode.Compose); - AddStepScreenModeTo(EditorScreenMode.Verify); - AddStepClickLink("00:30:000"); - AssertOnScreenAt(EditorScreenMode.Verify, 30_000); + addStepScreenModeTo(EditorScreenMode.Verify); + addStepClickLink("00:30:000"); + assertOnScreenAt(EditorScreenMode.Verify, 30_000); - AddStepClickLink("00:35:000 (0,1)"); - AssertMovedScreenTo(EditorScreenMode.Compose); + addStepClickLink("00:35:000 (0,1)"); + assertMovedScreenTo(EditorScreenMode.Compose); - AddStepClickLink("00:00:000"); - AssertOnScreenAt(EditorScreenMode.Compose, 0); - } - - [Test] - public void TestSelectionForOsu() - { - HitObject firstObject = null!; - RulesetInfo rulesetInfo = new OsuRuleset().RulesetInfo; - - SetUpEditor(rulesetInfo); - AddAssert("is editor Osu", () => EditorBeatmap.BeatmapInfo.Ruleset.Equals(rulesetInfo)); - - AddStepClickLink("00:00:956 (1,2,3)"); - AddAssert("snap and select 1-2-3", () => - { - firstObject = EditorBeatmap.HitObjects.First(); - return checkSnapAndSelectCombo(firstObject.StartTime, 1, 2, 3); - }); - - AddStepClickLink("00:01:450 (2,3,4,1,2)"); - AddAssert("snap and select 2-3-4-1-2", () => checkSnapAndSelectCombo(1_450, 2, 3, 4, 1, 2)); - - AddStepClickLink("00:00:956 (1,1,1)"); - AddAssert("snap and select 1-1-1", () => checkSnapAndSelectCombo(firstObject.StartTime, 1, 1, 1)); - } - - [Test] - public void TestUnusualSelectionForOsu() - { - HitObject firstObject = null!; - RulesetInfo rulesetInfo = new OsuRuleset().RulesetInfo; - - SetUpEditor(rulesetInfo); - AddAssert("is editor Osu", () => EditorBeatmap.BeatmapInfo.Ruleset.Equals(rulesetInfo)); - - AddStepClickLink("00:00:000 (1,2,3)", "invalid offset"); - AddAssert("snap to next, select 1-2-3", () => - { - firstObject = EditorBeatmap.HitObjects.First(); - return checkSnapAndSelectCombo(firstObject.StartTime, 1, 2, 3); - }); - - AddStepClickLink("00:00:956 (2,3,4)", "invalid offset"); - AddAssert("snap to next, select 2-3-4", () => checkSnapAndSelectCombo(firstObject.StartTime, 2, 3, 4)); - - AddStepClickLink("00:00:000 (0)", "invalid offset"); - AddAssert("snap and select 1", () => checkSnapAndSelectCombo(firstObject.StartTime, 1)); - - AddStepClickLink("00:00:000 (1)", "invalid offset"); - AddAssert("snap and select 1", () => checkSnapAndSelectCombo(firstObject.StartTime, 1)); - - AddStepClickLink("00:00:000 (2)", "invalid offset"); - AddAssert("snap and select 1", () => checkSnapAndSelectCombo(firstObject.StartTime, 1)); - - AddStepClickLink("00:00:000 (2,3)", "invalid offset"); - AddAssert("snap to 1, select 2-3", () => checkSnapAndSelectCombo(firstObject.StartTime, 2, 3)); - - AddStepClickLink("00:00:956 (956|1,956|2)", "mania link"); - AddAssert("snap to next, select none", () => checkSnapAndSelectCombo(firstObject.StartTime)); - - AddStepClickLink("00:00:000 (0|1)", "mania link"); - AddAssert("snap to 1, select none", () => checkSnapAndSelectCombo(firstObject.StartTime)); - } - - [Test] - public void TestSelectionForMania() - { - RulesetInfo rulesetInfo = new ManiaRuleset().RulesetInfo; - - SetUpEditor(rulesetInfo); - AddAssert("is editor Mania", () => EditorBeatmap.BeatmapInfo.Ruleset.Equals(rulesetInfo)); - - AddStepClickLink("00:11:010 (11010|1,11175|5,11258|3,11340|5,11505|1)"); - AddAssert("selected group", () => checkSnapAndSelectColumn(11010, new List<(int, int)> - { (11010, 1), (11175, 5), (11258, 3), (11340, 5), (11505, 1) } - )); - - AddStepClickLink("00:00:956 (956|1,956|6,1285|3,1780|4)"); - AddAssert("selected ungrouped", () => checkSnapAndSelectColumn(956, new List<(int, int)> - { (956, 1), (956, 6), (1285, 3), (1780, 4) } - )); - - AddStepClickLink("02:36:560 (156560|1,156560|4,156560|6)"); - AddAssert("selected in row", () => checkSnapAndSelectColumn(156560, new List<(int, int)> - { (156560, 1), (156560, 4), (156560, 6) } - )); - - AddStepClickLink("00:35:736 (35736|3,36395|3,36725|3,37384|3)"); - AddAssert("selected in column", () => checkSnapAndSelectColumn(35736, new List<(int, int)> - { (35736, 3), (36395, 3), (36725, 3), (37384, 3) } - )); - } - - [Test] - public void TestUnusualSelectionForMania() - { - RulesetInfo rulesetInfo = new ManiaRuleset().RulesetInfo; - - SetUpEditor(rulesetInfo); - AddAssert("is editor Mania", () => EditorBeatmap.BeatmapInfo.Ruleset.Equals(rulesetInfo)); - - AddStepClickLink("00:00:000 (0|1)", "invalid link"); - AddAssert("snap to 1, select none", () => checkSnapAndSelectColumn(956)); - - AddStepClickLink("00:00:000 (0)", "std link"); - AddAssert("snap and select 1", () => checkSnapAndSelectColumn(956, new List<(int, int)> - { (956, 1) }) - ); - - // TODO: discuss - this selects the first 2 objects on Stable, do we want that or is this fine? - AddStepClickLink("00:00:000 (1,2)", "std link"); - AddAssert("snap to 1, select none", () => checkSnapAndSelectColumn(956)); + addStepClickLink("00:00:000"); + assertOnScreenAt(EditorScreenMode.Compose, 0); } } } From 926636cc035a17543d987ba9a9ba9c47fa5e7f7f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Mu=CC=88ller-Ho=CC=88hne?= Date: Wed, 8 Nov 2023 19:43:54 +0900 Subject: [PATCH 15/91] Generalize Bezier curves to BSplines of Nth degree --- .../TestSceneJuiceStreamSelectionBlueprint.cs | 8 +- .../JuiceStreamPathTest.cs | 4 +- .../Mods/TestSceneCatchModPerfect.cs | 2 +- .../Mods/TestSceneCatchModRelax.cs | 2 +- .../TestSceneAutoJuiceStream.cs | 2 +- .../TestSceneCatchModHidden.cs | 2 +- .../TestSceneDrawableHitObjects.cs | 2 +- .../TestSceneJuiceStream.cs | 2 +- .../Blueprints/Components/EditablePath.cs | 2 +- .../Objects/JuiceStreamPath.cs | 2 +- .../Checks/CheckOffscreenObjectsTest.cs | 12 +-- .../Editor/TestSceneObjectMerging.cs | 10 +-- .../TestSceneOsuEditorSelectInvalidPath.cs | 2 +- .../TestScenePathControlPointVisualiser.cs | 36 ++++----- .../TestSceneSliderControlPointPiece.cs | 28 +++---- .../Editor/TestSceneSliderLengthValidity.cs | 8 +- .../TestSceneSliderPlacementBlueprint.cs | 42 +++++------ .../Editor/TestSceneSliderReversal.cs | 4 +- .../TestSceneSliderSelectionBlueprint.cs | 2 +- .../Editor/TestSceneSliderSnapping.cs | 10 +-- .../Editor/TestSceneSliderSplitting.cs | 28 +++---- .../Editor/TestSliderScaling.cs | 2 +- .../Mods/TestSceneOsuModHidden.cs | 10 +-- .../Mods/TestSceneOsuModPerfect.cs | 2 +- .../OsuHitObjectGenerationUtilsTest.cs | 6 +- .../TestSceneHitCircleLateFade.cs | 2 +- .../TestSceneLegacyHitPolicy.cs | 18 ++--- .../TestSceneSlider.cs | 14 ++-- .../TestSceneSliderApplication.cs | 6 +- .../TestSceneSliderFollowCircleInput.cs | 2 +- .../TestSceneSliderInput.cs | 8 +- .../TestSceneSliderSnaking.cs | 6 +- .../TestSceneStartTimeOrderedHitPolicy.cs | 10 +-- .../Components/PathControlPointPiece.cs | 20 +++-- .../Components/PathControlPointVisualiser.cs | 26 +++---- .../Sliders/SliderPlacementBlueprint.cs | 14 ++-- .../Edit/OsuSelectionHandler.cs | 4 +- osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs | 2 +- .../Formats/LegacyBeatmapDecoderTest.cs | 36 ++++----- .../Formats/LegacyBeatmapEncoderTest.cs | 6 +- .../Editing/LegacyEditorBeatmapPatcherTest.cs | 4 +- .../Editing/TestSceneEditorClipboard.cs | 2 +- .../Editing/TestSceneHitObjectComposer.cs | 2 +- .../Gameplay/TestSceneBezierConverter.cs | 40 +++++----- .../TestSceneGameplaySampleTriggerSource.cs | 2 +- .../Visual/Gameplay/TestSceneSliderPath.cs | 73 +++++++++++-------- .../Beatmaps/Formats/LegacyBeatmapEncoder.cs | 15 ++-- osu.Game/Database/LegacyBeatmapExporter.cs | 4 +- osu.Game/Rulesets/Objects/BezierConverter.cs | 32 ++++---- .../Objects/Legacy/ConvertHitObjectParser.cs | 20 ++--- osu.Game/Rulesets/Objects/SliderPath.cs | 13 ++-- .../Rulesets/Objects/SliderPathExtensions.cs | 6 +- osu.Game/Rulesets/Objects/Types/PathType.cs | 50 ++++++++++++- .../Screens/Play/HUD/ArgonHealthDisplay.cs | 10 +-- 54 files changed, 372 insertions(+), 305 deletions(-) diff --git a/osu.Game.Rulesets.Catch.Tests/Editor/TestSceneJuiceStreamSelectionBlueprint.cs b/osu.Game.Rulesets.Catch.Tests/Editor/TestSceneJuiceStreamSelectionBlueprint.cs index 05d7a38a95..16b51d414a 100644 --- a/osu.Game.Rulesets.Catch.Tests/Editor/TestSceneJuiceStreamSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Catch.Tests/Editor/TestSceneJuiceStreamSelectionBlueprint.cs @@ -140,7 +140,7 @@ namespace osu.Game.Rulesets.Catch.Tests.Editor AddStep("update hit object path", () => { - hitObject.Path = new SliderPath(PathType.PerfectCurve, new[] + hitObject.Path = new SliderPath(PathType.PERFECTCURVE, new[] { Vector2.Zero, new Vector2(100, 100), @@ -190,16 +190,16 @@ namespace osu.Game.Rulesets.Catch.Tests.Editor [Test] public void TestVertexResampling() { - addBlueprintStep(100, 100, new SliderPath(PathType.PerfectCurve, new[] + addBlueprintStep(100, 100, new SliderPath(PathType.PERFECTCURVE, new[] { Vector2.Zero, new Vector2(100, 100), new Vector2(50, 200), }), 0.5); AddAssert("1 vertex per 1 nested HO", () => getVertices().Count == hitObject.NestedHitObjects.Count); - AddAssert("slider path not yet changed", () => hitObject.Path.ControlPoints[0].Type == PathType.PerfectCurve); + AddAssert("slider path not yet changed", () => hitObject.Path.ControlPoints[0].Type == PathType.PERFECTCURVE); addAddVertexSteps(150, 150); - AddAssert("slider path change to linear", () => hitObject.Path.ControlPoints[0].Type == PathType.Linear); + AddAssert("slider path change to linear", () => hitObject.Path.ControlPoints[0].Type == PathType.LINEAR); } private void addBlueprintStep(double time, float x, SliderPath sliderPath, double velocity) => AddStep("add selection blueprint", () => diff --git a/osu.Game.Rulesets.Catch.Tests/JuiceStreamPathTest.cs b/osu.Game.Rulesets.Catch.Tests/JuiceStreamPathTest.cs index 95b4fdc07e..82f24633b5 100644 --- a/osu.Game.Rulesets.Catch.Tests/JuiceStreamPathTest.cs +++ b/osu.Game.Rulesets.Catch.Tests/JuiceStreamPathTest.cs @@ -154,7 +154,7 @@ namespace osu.Game.Rulesets.Catch.Tests } while (rng.Next(2) != 0); int length = sliderPath.ControlPoints.Count - start + 1; - sliderPath.ControlPoints[start].Type = length <= 2 ? PathType.Linear : length == 3 ? PathType.PerfectCurve : PathType.Bezier; + sliderPath.ControlPoints[start].Type = length <= 2 ? PathType.LINEAR : length == 3 ? PathType.PERFECTCURVE : PathType.BEZIER; } while (rng.Next(3) != 0); if (rng.Next(5) == 0) @@ -215,7 +215,7 @@ namespace osu.Game.Rulesets.Catch.Tests foreach (var point in sliderPath.ControlPoints) { - Assert.That(point.Type, Is.EqualTo(PathType.Linear).Or.Null); + Assert.That(point.Type, Is.EqualTo(PathType.LINEAR).Or.Null); Assert.That(sliderStartY + point.Position.Y, Is.InRange(0, JuiceStreamPath.OSU_PLAYFIELD_HEIGHT)); } diff --git a/osu.Game.Rulesets.Catch.Tests/Mods/TestSceneCatchModPerfect.cs b/osu.Game.Rulesets.Catch.Tests/Mods/TestSceneCatchModPerfect.cs index 71df523951..45e7d7aa28 100644 --- a/osu.Game.Rulesets.Catch.Tests/Mods/TestSceneCatchModPerfect.cs +++ b/osu.Game.Rulesets.Catch.Tests/Mods/TestSceneCatchModPerfect.cs @@ -35,7 +35,7 @@ namespace osu.Game.Rulesets.Catch.Tests.Mods var stream = new JuiceStream { StartTime = 1000, - Path = new SliderPath(PathType.Linear, new[] + Path = new SliderPath(PathType.LINEAR, new[] { Vector2.Zero, new Vector2(100, 0), diff --git a/osu.Game.Rulesets.Catch.Tests/Mods/TestSceneCatchModRelax.cs b/osu.Game.Rulesets.Catch.Tests/Mods/TestSceneCatchModRelax.cs index 5835ccaf78..a161615579 100644 --- a/osu.Game.Rulesets.Catch.Tests/Mods/TestSceneCatchModRelax.cs +++ b/osu.Game.Rulesets.Catch.Tests/Mods/TestSceneCatchModRelax.cs @@ -51,7 +51,7 @@ namespace osu.Game.Rulesets.Catch.Tests.Mods { X = CatchPlayfield.CENTER_X, StartTime = 3000, - Path = new SliderPath(PathType.Linear, new[] { Vector2.Zero, Vector2.UnitY * 200 }) + Path = new SliderPath(PathType.LINEAR, new[] { Vector2.Zero, Vector2.UnitY * 200 }) } } } diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneAutoJuiceStream.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneAutoJuiceStream.cs index 3261fb656e..202f010680 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneAutoJuiceStream.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneAutoJuiceStream.cs @@ -34,7 +34,7 @@ namespace osu.Game.Rulesets.Catch.Tests beatmap.HitObjects.Add(new JuiceStream { X = CatchPlayfield.CENTER_X - width / 2, - Path = new SliderPath(PathType.Linear, new[] + Path = new SliderPath(PathType.LINEAR, new[] { Vector2.Zero, new Vector2(width, 0) diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneCatchModHidden.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneCatchModHidden.cs index a44575a46e..419a846ec3 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneCatchModHidden.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneCatchModHidden.cs @@ -31,7 +31,7 @@ namespace osu.Game.Rulesets.Catch.Tests new JuiceStream { StartTime = 1000, - Path = new SliderPath(PathType.Linear, new[] { Vector2.Zero, new Vector2(0, -192) }), + Path = new SliderPath(PathType.LINEAR, new[] { Vector2.Zero, new Vector2(0, -192) }), X = CatchPlayfield.WIDTH / 2 } } diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneDrawableHitObjects.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneDrawableHitObjects.cs index 11d6419507..9c5cd68201 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneDrawableHitObjects.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneDrawableHitObjects.cs @@ -126,7 +126,7 @@ namespace osu.Game.Rulesets.Catch.Tests { X = xCoords, StartTime = playfieldTime + 1000, - Path = new SliderPath(PathType.Linear, new[] + Path = new SliderPath(PathType.LINEAR, new[] { Vector2.Zero, new Vector2(0, 200) diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneJuiceStream.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneJuiceStream.cs index c31a7ca99f..9a923adaab 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneJuiceStream.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneJuiceStream.cs @@ -32,7 +32,7 @@ namespace osu.Game.Rulesets.Catch.Tests new JuiceStream { X = CatchPlayfield.CENTER_X, - Path = new SliderPath(PathType.Linear, new[] + Path = new SliderPath(PathType.LINEAR, new[] { Vector2.Zero, new Vector2(0, 100) diff --git a/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/EditablePath.cs b/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/EditablePath.cs index df76bf0a8c..86f92d16ca 100644 --- a/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/EditablePath.cs +++ b/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/EditablePath.cs @@ -74,7 +74,7 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints.Components path.ConvertFromSliderPath(sliderPath, hitObject.Velocity); // If the original slider path has non-linear type segments, resample the vertices at nested hit object times to reduce the number of vertices. - if (sliderPath.ControlPoints.Any(p => p.Type != null && p.Type != PathType.Linear)) + if (sliderPath.ControlPoints.Any(p => p.Type != null && p.Type != PathType.LINEAR)) { path.ResampleVertices(hitObject.NestedHitObjects .Skip(1).TakeWhile(h => !(h is Fruit)) // Only droplets in the first span are used. diff --git a/osu.Game.Rulesets.Catch/Objects/JuiceStreamPath.cs b/osu.Game.Rulesets.Catch/Objects/JuiceStreamPath.cs index 0633151ddd..57acf7cee2 100644 --- a/osu.Game.Rulesets.Catch/Objects/JuiceStreamPath.cs +++ b/osu.Game.Rulesets.Catch/Objects/JuiceStreamPath.cs @@ -236,7 +236,7 @@ namespace osu.Game.Rulesets.Catch.Objects for (int i = 1; i < vertices.Count; i++) { - sliderPath.ControlPoints[^1].Type = PathType.Linear; + sliderPath.ControlPoints[^1].Type = PathType.LINEAR; float deltaX = vertices[i].X - lastPosition.X; double length = (vertices[i].Time - currentTime) * velocity; diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/Checks/CheckOffscreenObjectsTest.cs b/osu.Game.Rulesets.Osu.Tests/Editor/Checks/CheckOffscreenObjectsTest.cs index a72aaa966c..8612a8eb57 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/Checks/CheckOffscreenObjectsTest.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/Checks/CheckOffscreenObjectsTest.cs @@ -107,7 +107,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor.Checks Position = new Vector2(420, 240), Path = new SliderPath(new[] { - new PathControlPoint(new Vector2(0, 0), PathType.Linear), + new PathControlPoint(new Vector2(0, 0), PathType.LINEAR), new PathControlPoint(new Vector2(-100, 0)) }), } @@ -128,7 +128,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor.Checks Position = playfield_centre, Path = new SliderPath(new[] { - new PathControlPoint(new Vector2(0, 0), PathType.Linear), + new PathControlPoint(new Vector2(0, 0), PathType.LINEAR), new PathControlPoint(new Vector2(0, -playfield_centre.Y + 5)) }), } @@ -149,7 +149,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor.Checks Position = playfield_centre, Path = new SliderPath(new[] { - new PathControlPoint(new Vector2(0, 0), PathType.Linear), + new PathControlPoint(new Vector2(0, 0), PathType.LINEAR), new PathControlPoint(new Vector2(0, -playfield_centre.Y + 5)) }), StackHeight = 5 @@ -171,7 +171,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor.Checks Position = new Vector2(0, 0), Path = new SliderPath(new[] { - new PathControlPoint(new Vector2(0, 0), PathType.Linear), + new PathControlPoint(new Vector2(0, 0), PathType.LINEAR), new PathControlPoint(playfield_centre) }), } @@ -192,7 +192,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor.Checks Position = playfield_centre, Path = new SliderPath(new[] { - new PathControlPoint(new Vector2(0, 0), PathType.Linear), + new PathControlPoint(new Vector2(0, 0), PathType.LINEAR), new PathControlPoint(-playfield_centre) }), } @@ -214,7 +214,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor.Checks Path = new SliderPath(new[] { // Circular arc shoots over the top of the screen. - new PathControlPoint(new Vector2(0, 0), PathType.PerfectCurve), + new PathControlPoint(new Vector2(0, 0), PathType.PERFECTCURVE), new PathControlPoint(new Vector2(-100, -200)), new PathControlPoint(new Vector2(100, -200)) }), diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneObjectMerging.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneObjectMerging.cs index 8d8386cae1..3d35ab79f7 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneObjectMerging.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneObjectMerging.cs @@ -39,7 +39,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor mergeSelection(); AddAssert("slider created", () => circle1 is not null && circle2 is not null && sliderCreatedFor( - (pos: circle1.Position, pathType: PathType.Linear), + (pos: circle1.Position, pathType: PathType.LINEAR), (pos: circle2.Position, pathType: null))); AddStep("undo", () => Editor.Undo()); @@ -73,11 +73,11 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor var controlPoints = slider.Path.ControlPoints; (Vector2, PathType?)[] args = new (Vector2, PathType?)[controlPoints.Count + 2]; - args[0] = (circle1.Position, PathType.Linear); + args[0] = (circle1.Position, PathType.LINEAR); for (int i = 0; i < controlPoints.Count; i++) { - args[i + 1] = (controlPoints[i].Position + slider.Position, i == controlPoints.Count - 1 ? PathType.Linear : controlPoints[i].Type); + args[i + 1] = (controlPoints[i].Position + slider.Position, i == controlPoints.Count - 1 ? PathType.LINEAR : controlPoints[i].Type); } args[^1] = (circle2.Position, null); @@ -172,7 +172,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor mergeSelection(); AddAssert("slider created", () => circle1 is not null && circle2 is not null && sliderCreatedFor( - (pos: circle1.Position, pathType: PathType.Linear), + (pos: circle1.Position, pathType: PathType.LINEAR), (pos: circle2.Position, pathType: null))); AddAssert("samples exist", sliderSampleExist); @@ -227,7 +227,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor mergeSelection(); AddAssert("slider created", () => circle1 is not null && circle2 is not null && sliderCreatedFor( - (pos: circle1.Position, pathType: PathType.Linear), + (pos: circle1.Position, pathType: PathType.LINEAR), (pos: circle2.Position, pathType: null))); } diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditorSelectInvalidPath.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditorSelectInvalidPath.cs index 37a109de18..7ea4d40b90 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditorSelectInvalidPath.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditorSelectInvalidPath.cs @@ -25,7 +25,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor PathControlPoint[] points = { - new PathControlPoint(new Vector2(0), PathType.PerfectCurve), + new PathControlPoint(new Vector2(0), PathType.PERFECTCURVE), new PathControlPoint(new Vector2(-100, 0)), new PathControlPoint(new Vector2(100, 20)) }; diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestScenePathControlPointVisualiser.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestScenePathControlPointVisualiser.cs index c267cd1f63..16800997f4 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestScenePathControlPointVisualiser.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestScenePathControlPointVisualiser.cs @@ -52,7 +52,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor { createVisualiser(true); - addControlPointStep(new Vector2(200), PathType.Bezier); + addControlPointStep(new Vector2(200), PathType.BEZIER); addControlPointStep(new Vector2(300)); addControlPointStep(new Vector2(500, 300)); addControlPointStep(new Vector2(700, 200)); @@ -63,9 +63,9 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor AddStep("select control point", () => visualiser.Pieces[1].IsSelected.Value = true); addContextMenuItemStep("Perfect curve"); - assertControlPointPathType(0, PathType.Bezier); - assertControlPointPathType(1, PathType.PerfectCurve); - assertControlPointPathType(3, PathType.Bezier); + assertControlPointPathType(0, PathType.BEZIER); + assertControlPointPathType(1, PathType.PERFECTCURVE); + assertControlPointPathType(3, PathType.BEZIER); } [Test] @@ -73,7 +73,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor { createVisualiser(true); - addControlPointStep(new Vector2(200), PathType.Bezier); + addControlPointStep(new Vector2(200), PathType.BEZIER); addControlPointStep(new Vector2(300)); addControlPointStep(new Vector2(500, 300)); addControlPointStep(new Vector2(700, 200)); @@ -83,8 +83,8 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor AddStep("select control point", () => visualiser.Pieces[2].IsSelected.Value = true); addContextMenuItemStep("Perfect curve"); - assertControlPointPathType(0, PathType.Bezier); - assertControlPointPathType(2, PathType.PerfectCurve); + assertControlPointPathType(0, PathType.BEZIER); + assertControlPointPathType(2, PathType.PERFECTCURVE); assertControlPointPathType(4, null); } @@ -93,7 +93,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor { createVisualiser(true); - addControlPointStep(new Vector2(200), PathType.Bezier); + addControlPointStep(new Vector2(200), PathType.BEZIER); addControlPointStep(new Vector2(300)); addControlPointStep(new Vector2(500, 300)); addControlPointStep(new Vector2(700, 200)); @@ -103,7 +103,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor AddStep("select control point", () => visualiser.Pieces[3].IsSelected.Value = true); addContextMenuItemStep("Perfect curve"); - assertControlPointPathType(0, PathType.Bezier); + assertControlPointPathType(0, PathType.BEZIER); AddAssert("point 3 is not inherited", () => slider.Path.ControlPoints[3].Type != null); } @@ -112,7 +112,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor { createVisualiser(true); - addControlPointStep(new Vector2(200), PathType.Linear); + addControlPointStep(new Vector2(200), PathType.LINEAR); addControlPointStep(new Vector2(300)); addControlPointStep(new Vector2(500, 300)); addControlPointStep(new Vector2(700, 200)); @@ -123,9 +123,9 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor AddStep("select control point", () => visualiser.Pieces[1].IsSelected.Value = true); addContextMenuItemStep("Perfect curve"); - assertControlPointPathType(0, PathType.Linear); - assertControlPointPathType(1, PathType.PerfectCurve); - assertControlPointPathType(3, PathType.Linear); + assertControlPointPathType(0, PathType.LINEAR); + assertControlPointPathType(1, PathType.PERFECTCURVE); + assertControlPointPathType(3, PathType.LINEAR); } [Test] @@ -133,18 +133,18 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor { createVisualiser(true); - addControlPointStep(new Vector2(200), PathType.Bezier); - addControlPointStep(new Vector2(300), PathType.PerfectCurve); + addControlPointStep(new Vector2(200), PathType.BEZIER); + addControlPointStep(new Vector2(300), PathType.PERFECTCURVE); addControlPointStep(new Vector2(500, 300)); - addControlPointStep(new Vector2(700, 200), PathType.Bezier); + addControlPointStep(new Vector2(700, 200), PathType.BEZIER); addControlPointStep(new Vector2(500, 100)); moveMouseToControlPoint(3); AddStep("select control point", () => visualiser.Pieces[3].IsSelected.Value = true); addContextMenuItemStep("Inherit"); - assertControlPointPathType(0, PathType.Bezier); - assertControlPointPathType(1, PathType.Bezier); + assertControlPointPathType(0, PathType.BEZIER); + assertControlPointPathType(1, PathType.BEZIER); assertControlPointPathType(3, null); } diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderControlPointPiece.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderControlPointPiece.cs index 408205d6b2..1d8d2cf01a 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderControlPointPiece.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderControlPointPiece.cs @@ -38,9 +38,9 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor Position = new Vector2(256, 192), Path = new SliderPath(new[] { - new PathControlPoint(Vector2.Zero, PathType.PerfectCurve), + new PathControlPoint(Vector2.Zero, PathType.PERFECTCURVE), new PathControlPoint(new Vector2(150, 150)), - new PathControlPoint(new Vector2(300, 0), PathType.PerfectCurve), + new PathControlPoint(new Vector2(300, 0), PathType.PERFECTCURVE), new PathControlPoint(new Vector2(400, 0)), new PathControlPoint(new Vector2(400, 150)) }) @@ -182,7 +182,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor AddStep("release", () => InputManager.ReleaseButton(MouseButton.Left)); assertControlPointPosition(1, new Vector2(150, 50)); - assertControlPointType(0, PathType.PerfectCurve); + assertControlPointType(0, PathType.PERFECTCURVE); } [Test] @@ -210,7 +210,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor AddAssert("three control point pieces selected", () => this.ChildrenOfType>().Count(piece => piece.IsSelected.Value) == 3); assertControlPointPosition(2, new Vector2(450, 50)); - assertControlPointType(2, PathType.PerfectCurve); + assertControlPointType(2, PathType.PERFECTCURVE); assertControlPointPosition(3, new Vector2(550, 50)); @@ -249,7 +249,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor AddAssert("slider moved", () => Precision.AlmostEquals(slider.Position, new Vector2(256, 192) + new Vector2(150, 50))); assertControlPointPosition(0, Vector2.Zero); - assertControlPointType(0, PathType.PerfectCurve); + assertControlPointType(0, PathType.PERFECTCURVE); assertControlPointPosition(1, new Vector2(0, 100)); @@ -272,7 +272,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor AddStep("release", () => InputManager.ReleaseButton(MouseButton.Left)); assertControlPointPosition(1, new Vector2(400, 0.01f)); - assertControlPointType(0, PathType.Bezier); + assertControlPointType(0, PathType.BEZIER); } [Test] @@ -282,13 +282,13 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor AddStep("hold", () => InputManager.PressButton(MouseButton.Left)); addMovementStep(new Vector2(400, 0.01f)); - assertControlPointType(0, PathType.Bezier); + assertControlPointType(0, PathType.BEZIER); addMovementStep(new Vector2(150, 50)); AddStep("release", () => InputManager.ReleaseButton(MouseButton.Left)); assertControlPointPosition(1, new Vector2(150, 50)); - assertControlPointType(0, PathType.PerfectCurve); + assertControlPointType(0, PathType.PERFECTCURVE); } [Test] @@ -298,32 +298,32 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor AddStep("hold", () => InputManager.PressButton(MouseButton.Left)); addMovementStep(new Vector2(350, 0.01f)); - assertControlPointType(2, PathType.Bezier); + assertControlPointType(2, PathType.BEZIER); addMovementStep(new Vector2(150, 150)); AddStep("release", () => InputManager.ReleaseButton(MouseButton.Left)); assertControlPointPosition(4, new Vector2(150, 150)); - assertControlPointType(2, PathType.PerfectCurve); + assertControlPointType(2, PathType.PERFECTCURVE); } [Test] public void TestDragControlPointPathAfterChangingType() { - AddStep("change type to bezier", () => slider.Path.ControlPoints[2].Type = PathType.Bezier); + AddStep("change type to bezier", () => slider.Path.ControlPoints[2].Type = PathType.BEZIER); AddStep("add point", () => slider.Path.ControlPoints.Add(new PathControlPoint(new Vector2(500, 10)))); - AddStep("change type to perfect", () => slider.Path.ControlPoints[3].Type = PathType.PerfectCurve); + AddStep("change type to perfect", () => slider.Path.ControlPoints[3].Type = PathType.PERFECTCURVE); moveMouseToControlPoint(4); AddStep("hold", () => InputManager.PressButton(MouseButton.Left)); - assertControlPointType(3, PathType.PerfectCurve); + assertControlPointType(3, PathType.PERFECTCURVE); addMovementStep(new Vector2(350, 0.01f)); AddStep("release", () => InputManager.ReleaseButton(MouseButton.Left)); assertControlPointPosition(4, new Vector2(350, 0.01f)); - assertControlPointType(3, PathType.Bezier); + assertControlPointType(3, PathType.BEZIER); } private void addMovementStep(Vector2 relativePosition) diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderLengthValidity.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderLengthValidity.cs index 77e828e80a..38ebeb7e8f 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderLengthValidity.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderLengthValidity.cs @@ -43,7 +43,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor PathControlPoint[] points = { - new PathControlPoint(new Vector2(0), PathType.Linear), + new PathControlPoint(new Vector2(0), PathType.LINEAR), new PathControlPoint(new Vector2(100, 0)), }; @@ -82,7 +82,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor PathControlPoint[] points = { - new PathControlPoint(new Vector2(0), PathType.Linear), + new PathControlPoint(new Vector2(0), PathType.LINEAR), new PathControlPoint(new Vector2(100, 0)), }; @@ -126,7 +126,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor PathControlPoint[] points = { - new PathControlPoint(new Vector2(0), PathType.PerfectCurve), + new PathControlPoint(new Vector2(0), PathType.PERFECTCURVE), new PathControlPoint(new Vector2(100, 0)), new PathControlPoint(new Vector2(0, 10)) }; @@ -165,7 +165,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor PathControlPoint[] points = { - new PathControlPoint(new Vector2(0), PathType.Linear), + new PathControlPoint(new Vector2(0), PathType.LINEAR), new PathControlPoint(new Vector2(0, 50)), new PathControlPoint(new Vector2(0, 100)) }; diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderPlacementBlueprint.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderPlacementBlueprint.cs index 7d29670daa..4b120c1a3f 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderPlacementBlueprint.cs @@ -58,7 +58,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor assertPlaced(true); assertLength(200); assertControlPointCount(2); - assertControlPointType(0, PathType.Linear); + assertControlPointType(0, PathType.LINEAR); } [Test] @@ -72,7 +72,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor assertPlaced(true); assertControlPointCount(2); - assertControlPointType(0, PathType.Linear); + assertControlPointType(0, PathType.LINEAR); } [Test] @@ -90,7 +90,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor assertPlaced(true); assertControlPointCount(3); assertControlPointPosition(1, new Vector2(100, 0)); - assertControlPointType(0, PathType.PerfectCurve); + assertControlPointType(0, PathType.PERFECTCURVE); } [Test] @@ -112,7 +112,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor assertControlPointCount(4); assertControlPointPosition(1, new Vector2(100, 0)); assertControlPointPosition(2, new Vector2(100, 100)); - assertControlPointType(0, PathType.Bezier); + assertControlPointType(0, PathType.BEZIER); } [Test] @@ -131,8 +131,8 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor assertPlaced(true); assertControlPointCount(3); assertControlPointPosition(1, new Vector2(100, 0)); - assertControlPointType(0, PathType.Linear); - assertControlPointType(1, PathType.Linear); + assertControlPointType(0, PathType.LINEAR); + assertControlPointType(1, PathType.LINEAR); } [Test] @@ -150,7 +150,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor assertPlaced(true); assertControlPointCount(2); - assertControlPointType(0, PathType.Linear); + assertControlPointType(0, PathType.LINEAR); assertLength(100); } @@ -172,7 +172,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor assertPlaced(true); assertControlPointCount(3); - assertControlPointType(0, PathType.PerfectCurve); + assertControlPointType(0, PathType.PERFECTCURVE); } [Test] @@ -196,7 +196,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor assertPlaced(true); assertControlPointCount(4); - assertControlPointType(0, PathType.Bezier); + assertControlPointType(0, PathType.BEZIER); } [Test] @@ -216,8 +216,8 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor assertControlPointCount(3); assertControlPointPosition(1, new Vector2(100, 0)); assertControlPointPosition(2, new Vector2(100)); - assertControlPointType(0, PathType.Linear); - assertControlPointType(1, PathType.Linear); + assertControlPointType(0, PathType.LINEAR); + assertControlPointType(1, PathType.LINEAR); } [Test] @@ -240,8 +240,8 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor assertControlPointCount(4); assertControlPointPosition(1, new Vector2(100, 0)); assertControlPointPosition(2, new Vector2(100)); - assertControlPointType(0, PathType.Linear); - assertControlPointType(1, PathType.PerfectCurve); + assertControlPointType(0, PathType.LINEAR); + assertControlPointType(1, PathType.PERFECTCURVE); } [Test] @@ -269,8 +269,8 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor assertControlPointPosition(2, new Vector2(100)); assertControlPointPosition(3, new Vector2(200, 100)); assertControlPointPosition(4, new Vector2(200)); - assertControlPointType(0, PathType.PerfectCurve); - assertControlPointType(2, PathType.PerfectCurve); + assertControlPointType(0, PathType.PERFECTCURVE); + assertControlPointType(2, PathType.PERFECTCURVE); } [Test] @@ -287,7 +287,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor assertPlaced(true); assertLength(200); assertControlPointCount(2); - assertControlPointType(0, PathType.Linear); + assertControlPointType(0, PathType.LINEAR); } [Test] @@ -306,7 +306,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor assertPlaced(true); assertControlPointCount(3); - assertControlPointType(0, PathType.Bezier); + assertControlPointType(0, PathType.BEZIER); } [Test] @@ -326,7 +326,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor assertPlaced(true); assertControlPointCount(3); - assertControlPointType(0, PathType.PerfectCurve); + assertControlPointType(0, PathType.PERFECTCURVE); } [Test] @@ -347,7 +347,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor assertPlaced(true); assertControlPointCount(3); - assertControlPointType(0, PathType.PerfectCurve); + assertControlPointType(0, PathType.PERFECTCURVE); } [Test] @@ -368,7 +368,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor assertPlaced(true); assertControlPointCount(3); - assertControlPointType(0, PathType.Bezier); + assertControlPointType(0, PathType.BEZIER); } [Test] @@ -385,7 +385,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor assertPlaced(true); assertControlPointCount(3); - assertControlPointType(0, PathType.PerfectCurve); + assertControlPointType(0, PathType.PERFECTCURVE); } private void addMovementStep(Vector2 position) => AddStep($"move mouse to {position}", () => InputManager.MoveMouseTo(InputManager.ToScreenSpace(position))); diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderReversal.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderReversal.cs index 9c5eb83e3c..0ddfc40946 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderReversal.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderReversal.cs @@ -22,12 +22,12 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor private readonly PathControlPoint[][] paths = { createPathSegment( - PathType.PerfectCurve, + PathType.PERFECTCURVE, new Vector2(200, -50), new Vector2(250, 0) ), createPathSegment( - PathType.Linear, + PathType.LINEAR, new Vector2(100, 0), new Vector2(100, 100) ) diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSelectionBlueprint.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSelectionBlueprint.cs index 413a3c3dfd..d4d99e1019 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSelectionBlueprint.cs @@ -34,7 +34,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor slider = new Slider { Position = new Vector2(256, 192), - Path = new SliderPath(PathType.Bezier, new[] + Path = new SliderPath(PathType.BEZIER, new[] { Vector2.Zero, new Vector2(150, 150), diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSnapping.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSnapping.cs index 0ae14bdde8..c984d9168e 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSnapping.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSnapping.cs @@ -56,7 +56,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor { ControlPoints = { - new PathControlPoint(Vector2.Zero, PathType.PerfectCurve), + new PathControlPoint(Vector2.Zero, PathType.PERFECTCURVE), new PathControlPoint(new Vector2(136, 205)), new PathControlPoint(new Vector2(-4, 226)) } @@ -181,7 +181,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor { OsuSelectionHandler selectionHandler; - AddAssert("first control point perfect", () => slider.Path.ControlPoints[0].Type == PathType.PerfectCurve); + AddAssert("first control point perfect", () => slider.Path.ControlPoints[0].Type == PathType.PERFECTCURVE); AddStep("select slider", () => EditorBeatmap.SelectedHitObjects.Add(slider)); AddStep("rotate 90 degrees ccw", () => @@ -190,7 +190,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor selectionHandler.HandleRotation(-90); }); - AddAssert("first control point still perfect", () => slider.Path.ControlPoints[0].Type == PathType.PerfectCurve); + AddAssert("first control point still perfect", () => slider.Path.ControlPoints[0].Type == PathType.PERFECTCURVE); } [Test] @@ -223,7 +223,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor { OsuSelectionHandler selectionHandler; - AddAssert("first control point perfect", () => slider.Path.ControlPoints[0].Type == PathType.PerfectCurve); + AddAssert("first control point perfect", () => slider.Path.ControlPoints[0].Type == PathType.PERFECTCURVE); AddStep("select slider", () => EditorBeatmap.SelectedHitObjects.Add(slider)); AddStep("flip slider horizontally", () => @@ -232,7 +232,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor selectionHandler.OnPressed(new KeyBindingPressEvent(InputManager.CurrentState, GlobalAction.EditorFlipVertically)); }); - AddAssert("first control point still perfect", () => slider.Path.ControlPoints[0].Type == PathType.PerfectCurve); + AddAssert("first control point still perfect", () => slider.Path.ControlPoints[0].Type == PathType.PERFECTCURVE); } [Test] diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSplitting.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSplitting.cs index ad37258c9b..cded9165f4 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSplitting.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSplitting.cs @@ -45,9 +45,9 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor Position = new Vector2(0, 50), Path = new SliderPath(new[] { - new PathControlPoint(Vector2.Zero, PathType.PerfectCurve), + new PathControlPoint(Vector2.Zero, PathType.PERFECTCURVE), new PathControlPoint(new Vector2(150, 150)), - new PathControlPoint(new Vector2(300, 0), PathType.PerfectCurve), + new PathControlPoint(new Vector2(300, 0), PathType.PERFECTCURVE), new PathControlPoint(new Vector2(400, 0)), new PathControlPoint(new Vector2(400, 150)) }) @@ -73,20 +73,20 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor AddAssert("slider split", () => slider is not null && EditorBeatmap.HitObjects.Count == 2 && sliderCreatedFor((Slider)EditorBeatmap.HitObjects[0], 0, EditorBeatmap.HitObjects[1].StartTime - split_gap, - (new Vector2(0, 50), PathType.PerfectCurve), + (new Vector2(0, 50), PathType.PERFECTCURVE), (new Vector2(150, 200), null), (new Vector2(300, 50), null) ) && sliderCreatedFor((Slider)EditorBeatmap.HitObjects[1], slider.StartTime, endTime + split_gap, - (new Vector2(300, 50), PathType.PerfectCurve), + (new Vector2(300, 50), PathType.PERFECTCURVE), (new Vector2(400, 50), null), (new Vector2(400, 200), null) )); AddStep("undo", () => Editor.Undo()); AddAssert("original slider restored", () => EditorBeatmap.HitObjects.Count == 1 && sliderCreatedFor((Slider)EditorBeatmap.HitObjects[0], 0, endTime, - (new Vector2(0, 50), PathType.PerfectCurve), + (new Vector2(0, 50), PathType.PERFECTCURVE), (new Vector2(150, 200), null), - (new Vector2(300, 50), PathType.PerfectCurve), + (new Vector2(300, 50), PathType.PERFECTCURVE), (new Vector2(400, 50), null), (new Vector2(400, 200), null) )); @@ -104,11 +104,11 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor Position = new Vector2(0, 50), Path = new SliderPath(new[] { - new PathControlPoint(Vector2.Zero, PathType.PerfectCurve), + new PathControlPoint(Vector2.Zero, PathType.PERFECTCURVE), new PathControlPoint(new Vector2(150, 150)), - new PathControlPoint(new Vector2(300, 0), PathType.Bezier), + new PathControlPoint(new Vector2(300, 0), PathType.BEZIER), new PathControlPoint(new Vector2(400, 0)), - new PathControlPoint(new Vector2(400, 150), PathType.Catmull), + new PathControlPoint(new Vector2(400, 150), PathType.CATMULL), new PathControlPoint(new Vector2(300, 200)), new PathControlPoint(new Vector2(400, 250)) }) @@ -139,15 +139,15 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor AddAssert("slider split", () => slider is not null && EditorBeatmap.HitObjects.Count == 3 && sliderCreatedFor((Slider)EditorBeatmap.HitObjects[0], 0, EditorBeatmap.HitObjects[1].StartTime - split_gap, - (new Vector2(0, 50), PathType.PerfectCurve), + (new Vector2(0, 50), PathType.PERFECTCURVE), (new Vector2(150, 200), null), (new Vector2(300, 50), null) ) && sliderCreatedFor((Slider)EditorBeatmap.HitObjects[1], EditorBeatmap.HitObjects[0].GetEndTime() + split_gap, slider.StartTime - split_gap, - (new Vector2(300, 50), PathType.Bezier), + (new Vector2(300, 50), PathType.BEZIER), (new Vector2(400, 50), null), (new Vector2(400, 200), null) ) && sliderCreatedFor((Slider)EditorBeatmap.HitObjects[2], EditorBeatmap.HitObjects[1].GetEndTime() + split_gap, endTime + split_gap * 2, - (new Vector2(400, 200), PathType.Catmull), + (new Vector2(400, 200), PathType.CATMULL), (new Vector2(300, 250), null), (new Vector2(400, 300), null) )); @@ -165,9 +165,9 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor Position = new Vector2(0, 50), Path = new SliderPath(new[] { - new PathControlPoint(Vector2.Zero, PathType.PerfectCurve), + new PathControlPoint(Vector2.Zero, PathType.PERFECTCURVE), new PathControlPoint(new Vector2(150, 150)), - new PathControlPoint(new Vector2(300, 0), PathType.PerfectCurve), + new PathControlPoint(new Vector2(300, 0), PathType.PERFECTCURVE), new PathControlPoint(new Vector2(400, 0)), new PathControlPoint(new Vector2(400, 150)) }) diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSliderScaling.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSliderScaling.cs index 64d23090d0..021fdba225 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSliderScaling.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSliderScaling.cs @@ -43,7 +43,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor PathControlPoint[] points = { - new PathControlPoint(new Vector2(0), PathType.Linear), + new PathControlPoint(new Vector2(0), PathType.LINEAR), new PathControlPoint(new Vector2(100, 0)), }; diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModHidden.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModHidden.cs index 3f84ac6935..58bdd805c1 100644 --- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModHidden.cs +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModHidden.cs @@ -81,12 +81,12 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods new Slider { StartTime = 3200, - Path = new SliderPath(PathType.Linear, new[] { Vector2.Zero, new Vector2(100, 0), }) + Path = new SliderPath(PathType.LINEAR, new[] { Vector2.Zero, new Vector2(100, 0), }) }, new Slider { StartTime = 5200, - Path = new SliderPath(PathType.Linear, new[] { Vector2.Zero, new Vector2(100, 0), }) + Path = new SliderPath(PathType.LINEAR, new[] { Vector2.Zero, new Vector2(100, 0), }) } } }, @@ -105,12 +105,12 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods new Slider { StartTime = 1000, - Path = new SliderPath(PathType.Linear, new[] { Vector2.Zero, new Vector2(100, 0), }) + Path = new SliderPath(PathType.LINEAR, new[] { Vector2.Zero, new Vector2(100, 0), }) }, new Slider { StartTime = 4000, - Path = new SliderPath(PathType.Linear, new[] { Vector2.Zero, new Vector2(100, 0), }) + Path = new SliderPath(PathType.LINEAR, new[] { Vector2.Zero, new Vector2(100, 0), }) }, } }, @@ -140,7 +140,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods { StartTime = 3000, Position = new Vector2(156, 242), - Path = new SliderPath(PathType.Linear, new[] { Vector2.Zero, new Vector2(200, 0), }) + Path = new SliderPath(PathType.LINEAR, new[] { Vector2.Zero, new Vector2(200, 0), }) }, new Spinner { diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModPerfect.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModPerfect.cs index f0496efc19..26c4133bc4 100644 --- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModPerfect.cs +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModPerfect.cs @@ -31,7 +31,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods var slider = new Slider { StartTime = 1000, - Path = new SliderPath(PathType.Linear, new[] { Vector2.Zero, new Vector2(100, 0), }) + Path = new SliderPath(PathType.LINEAR, new[] { Vector2.Zero, new Vector2(100, 0), }) }; CreateHitObjectTest(new HitObjectTestData(slider), shouldMiss); diff --git a/osu.Game.Rulesets.Osu.Tests/OsuHitObjectGenerationUtilsTest.cs b/osu.Game.Rulesets.Osu.Tests/OsuHitObjectGenerationUtilsTest.cs index daa914cac2..d78c32aa6a 100644 --- a/osu.Game.Rulesets.Osu.Tests/OsuHitObjectGenerationUtilsTest.cs +++ b/osu.Game.Rulesets.Osu.Tests/OsuHitObjectGenerationUtilsTest.cs @@ -26,9 +26,9 @@ namespace osu.Game.Rulesets.Osu.Tests { ControlPoints = { - new PathControlPoint(new Vector2(), PathType.Linear), - new PathControlPoint(new Vector2(-64, -128), PathType.Linear), // absolute position: (64, 0) - new PathControlPoint(new Vector2(-128, 0), PathType.Linear) // absolute position: (0, 128) + new PathControlPoint(new Vector2(), PathType.LINEAR), + new PathControlPoint(new Vector2(-64, -128), PathType.LINEAR), // absolute position: (64, 0) + new PathControlPoint(new Vector2(-128, 0), PathType.LINEAR) // absolute position: (0, 128) } }, RepeatCount = 1 diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleLateFade.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleLateFade.cs index 483155e646..7824f26251 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleLateFade.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleLateFade.cs @@ -167,7 +167,7 @@ namespace osu.Game.Rulesets.Osu.Tests { StartTime = Time.Current + 500, Position = new Vector2(250), - Path = new SliderPath(PathType.Linear, new[] + Path = new SliderPath(PathType.LINEAR, new[] { Vector2.Zero, new Vector2(0, 100), diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneLegacyHitPolicy.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneLegacyHitPolicy.cs index fa6aa580a3..e460da9bd5 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneLegacyHitPolicy.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneLegacyHitPolicy.cs @@ -264,7 +264,7 @@ namespace osu.Game.Rulesets.Osu.Tests { StartTime = time_slider, Position = positionSlider, - Path = new SliderPath(PathType.Linear, new[] + Path = new SliderPath(PathType.LINEAR, new[] { Vector2.Zero, new Vector2(50, 0), @@ -308,7 +308,7 @@ namespace osu.Game.Rulesets.Osu.Tests { StartTime = time_slider, Position = positionSlider, - Path = new SliderPath(PathType.Linear, new[] + Path = new SliderPath(PathType.LINEAR, new[] { Vector2.Zero, new Vector2(50, 0), @@ -391,7 +391,7 @@ namespace osu.Game.Rulesets.Osu.Tests { StartTime = time_slider, Position = positionSlider, - Path = new SliderPath(PathType.Linear, new[] + Path = new SliderPath(PathType.LINEAR, new[] { Vector2.Zero, new Vector2(25, 0), @@ -428,7 +428,7 @@ namespace osu.Game.Rulesets.Osu.Tests { StartTime = time_first_slider, Position = positionFirstSlider, - Path = new SliderPath(PathType.Linear, new[] + Path = new SliderPath(PathType.LINEAR, new[] { Vector2.Zero, new Vector2(25, 0), @@ -438,7 +438,7 @@ namespace osu.Game.Rulesets.Osu.Tests { StartTime = time_second_slider, Position = positionSecondSlider, - Path = new SliderPath(PathType.Linear, new[] + Path = new SliderPath(PathType.LINEAR, new[] { Vector2.Zero, new Vector2(25, 0), @@ -521,7 +521,7 @@ namespace osu.Game.Rulesets.Osu.Tests { StartTime = time_first_slider, Position = positionFirstSlider, - Path = new SliderPath(PathType.Linear, new[] + Path = new SliderPath(PathType.LINEAR, new[] { Vector2.Zero, new Vector2(25, 0), @@ -531,7 +531,7 @@ namespace osu.Game.Rulesets.Osu.Tests { StartTime = time_second_slider, Position = positionSecondSlider, - Path = new SliderPath(PathType.Linear, new[] + Path = new SliderPath(PathType.LINEAR, new[] { Vector2.Zero, new Vector2(25, 0), @@ -571,7 +571,7 @@ namespace osu.Game.Rulesets.Osu.Tests { StartTime = time_first_slider, Position = positionFirstSlider, - Path = new SliderPath(PathType.Linear, new[] + Path = new SliderPath(PathType.LINEAR, new[] { Vector2.Zero, new Vector2(25, 0), @@ -581,7 +581,7 @@ namespace osu.Game.Rulesets.Osu.Tests { StartTime = time_second_slider, Position = positionSecondSlider, - Path = new SliderPath(PathType.Linear, new[] + Path = new SliderPath(PathType.LINEAR, new[] { Vector2.Zero, new Vector2(25, 0), diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSlider.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSlider.cs index b805e7ed63..60003e7950 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSlider.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSlider.cs @@ -219,7 +219,7 @@ namespace osu.Game.Rulesets.Osu.Tests { StartTime = Time.Current + time_offset, Position = new Vector2(239, 176), - Path = new SliderPath(PathType.PerfectCurve, new[] + Path = new SliderPath(PathType.PERFECTCURVE, new[] { Vector2.Zero, new Vector2(154, 28), @@ -255,7 +255,7 @@ namespace osu.Game.Rulesets.Osu.Tests SliderVelocityMultiplier = speedMultiplier, StartTime = Time.Current + time_offset, Position = new Vector2(0, -(distance / 2)), - Path = new SliderPath(PathType.PerfectCurve, new[] + Path = new SliderPath(PathType.PERFECTCURVE, new[] { Vector2.Zero, new Vector2(0, distance), @@ -273,7 +273,7 @@ namespace osu.Game.Rulesets.Osu.Tests { StartTime = Time.Current + time_offset, Position = new Vector2(-max_length / 2, 0), - Path = new SliderPath(PathType.PerfectCurve, new[] + Path = new SliderPath(PathType.PERFECTCURVE, new[] { Vector2.Zero, new Vector2(max_length / 2, max_length / 2), @@ -293,7 +293,7 @@ namespace osu.Game.Rulesets.Osu.Tests { StartTime = Time.Current + time_offset, Position = new Vector2(-max_length / 2, 0), - Path = new SliderPath(PathType.Linear, new[] + Path = new SliderPath(PathType.LINEAR, new[] { Vector2.Zero, new Vector2(max_length * 0.375f, max_length * 0.18f), @@ -316,7 +316,7 @@ namespace osu.Game.Rulesets.Osu.Tests { StartTime = Time.Current + time_offset, Position = new Vector2(-max_length / 2, 0), - Path = new SliderPath(PathType.Bezier, new[] + Path = new SliderPath(PathType.BEZIER, new[] { Vector2.Zero, new Vector2(max_length * 0.375f, max_length * 0.18f), @@ -338,7 +338,7 @@ namespace osu.Game.Rulesets.Osu.Tests { StartTime = Time.Current + time_offset, Position = new Vector2(0, 0), - Path = new SliderPath(PathType.Linear, new[] + Path = new SliderPath(PathType.LINEAR, new[] { Vector2.Zero, new Vector2(-max_length / 2, 0), @@ -365,7 +365,7 @@ namespace osu.Game.Rulesets.Osu.Tests { StartTime = Time.Current + time_offset, Position = new Vector2(-max_length / 4, 0), - Path = new SliderPath(PathType.Catmull, new[] + Path = new SliderPath(PathType.CATMULL, new[] { Vector2.Zero, new Vector2(max_length * 0.125f, max_length * 0.125f), diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderApplication.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderApplication.cs index 88b70a8836..f41dd913ab 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderApplication.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderApplication.cs @@ -37,7 +37,7 @@ namespace osu.Game.Rulesets.Osu.Tests Position = new Vector2(256, 192), IndexInCurrentCombo = 0, StartTime = Time.Current, - Path = new SliderPath(PathType.Linear, new[] + Path = new SliderPath(PathType.LINEAR, new[] { Vector2.Zero, new Vector2(150, 100), @@ -52,7 +52,7 @@ namespace osu.Game.Rulesets.Osu.Tests Position = new Vector2(256, 192), ComboIndex = 1, StartTime = dho.HitObject.StartTime, - Path = new SliderPath(PathType.Bezier, new[] + Path = new SliderPath(PathType.BEZIER, new[] { Vector2.Zero, new Vector2(150, 100), @@ -80,7 +80,7 @@ namespace osu.Game.Rulesets.Osu.Tests Position = new Vector2(256, 192), IndexInCurrentCombo = 0, StartTime = Time.Current, - Path = new SliderPath(PathType.Linear, new[] + Path = new SliderPath(PathType.LINEAR, new[] { Vector2.Zero, new Vector2(150, 100), diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderFollowCircleInput.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderFollowCircleInput.cs index d4bb789a12..fc9bb16cb7 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderFollowCircleInput.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderFollowCircleInput.cs @@ -48,7 +48,7 @@ namespace osu.Game.Rulesets.Osu.Tests StartTime = time_slider_start, Position = new Vector2(0, 0), SliderVelocityMultiplier = velocity, - Path = new SliderPath(PathType.Linear, new[] + Path = new SliderPath(PathType.LINEAR, new[] { Vector2.Zero, new Vector2(followCircleRadius, 0), diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderInput.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderInput.cs index f718a5069f..08836ef819 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderInput.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderInput.cs @@ -62,7 +62,7 @@ namespace osu.Game.Rulesets.Osu.Tests Position = new Vector2(0, 0), SliderVelocityMultiplier = 10f, RepeatCount = repeatCount, - Path = new SliderPath(PathType.Linear, new[] + Path = new SliderPath(PathType.LINEAR, new[] { Vector2.Zero, new Vector2(sliderLength, 0), @@ -103,7 +103,7 @@ namespace osu.Game.Rulesets.Osu.Tests Position = new Vector2(0, 0), SliderVelocityMultiplier = 10f, RepeatCount = repeatCount, - Path = new SliderPath(PathType.Linear, new[] + Path = new SliderPath(PathType.LINEAR, new[] { Vector2.Zero, new Vector2(sliderLength, 0), @@ -145,7 +145,7 @@ namespace osu.Game.Rulesets.Osu.Tests StartTime = time_slider_start, Position = new Vector2(0, 0), SliderVelocityMultiplier = 10f, - Path = new SliderPath(PathType.Linear, new[] + Path = new SliderPath(PathType.LINEAR, new[] { Vector2.Zero, new Vector2(slider_path_length * 10, 0), @@ -478,7 +478,7 @@ namespace osu.Game.Rulesets.Osu.Tests StartTime = time_slider_start, Position = new Vector2(0, 0), SliderVelocityMultiplier = 0.1f, - Path = new SliderPath(PathType.PerfectCurve, new[] + Path = new SliderPath(PathType.PERFECTCURVE, new[] { Vector2.Zero, new Vector2(slider_path_length, 0), diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs index 13166c2b6b..ebc5143aed 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs @@ -217,7 +217,7 @@ namespace osu.Game.Rulesets.Osu.Tests { StartTime = 3000, Position = new Vector2(100, 100), - Path = new SliderPath(PathType.PerfectCurve, new[] + Path = new SliderPath(PathType.PERFECTCURVE, new[] { Vector2.Zero, new Vector2(300, 200) @@ -227,7 +227,7 @@ namespace osu.Game.Rulesets.Osu.Tests { StartTime = 13000, Position = new Vector2(100, 100), - Path = new SliderPath(PathType.PerfectCurve, new[] + Path = new SliderPath(PathType.PERFECTCURVE, new[] { Vector2.Zero, new Vector2(300, 200) @@ -238,7 +238,7 @@ namespace osu.Game.Rulesets.Osu.Tests { StartTime = 23000, Position = new Vector2(100, 100), - Path = new SliderPath(PathType.PerfectCurve, new[] + Path = new SliderPath(PathType.PERFECTCURVE, new[] { Vector2.Zero, new Vector2(300, 200) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneStartTimeOrderedHitPolicy.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneStartTimeOrderedHitPolicy.cs index 3475680c71..895e9bbdee 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneStartTimeOrderedHitPolicy.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneStartTimeOrderedHitPolicy.cs @@ -196,7 +196,7 @@ namespace osu.Game.Rulesets.Osu.Tests { StartTime = time_slider, Position = positionSlider, - Path = new SliderPath(PathType.Linear, new[] + Path = new SliderPath(PathType.LINEAR, new[] { Vector2.Zero, new Vector2(25, 0), @@ -238,7 +238,7 @@ namespace osu.Game.Rulesets.Osu.Tests { StartTime = time_slider, Position = positionSlider, - Path = new SliderPath(PathType.Linear, new[] + Path = new SliderPath(PathType.LINEAR, new[] { Vector2.Zero, new Vector2(25, 0), @@ -318,7 +318,7 @@ namespace osu.Game.Rulesets.Osu.Tests { StartTime = time_slider, Position = positionSlider, - Path = new SliderPath(PathType.Linear, new[] + Path = new SliderPath(PathType.LINEAR, new[] { Vector2.Zero, new Vector2(25, 0), @@ -352,7 +352,7 @@ namespace osu.Game.Rulesets.Osu.Tests { StartTime = time_first_slider, Position = positionFirstSlider, - Path = new SliderPath(PathType.Linear, new[] + Path = new SliderPath(PathType.LINEAR, new[] { Vector2.Zero, new Vector2(25, 0), @@ -362,7 +362,7 @@ namespace osu.Game.Rulesets.Osu.Tests { StartTime = time_second_slider, Position = positionSecondSlider, - Path = new SliderPath(PathType.Linear, new[] + Path = new SliderPath(PathType.LINEAR, new[] { Vector2.Zero, new Vector2(25, 0), diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs index 12e5ca0236..9658e5f6c3 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs @@ -221,11 +221,11 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components /// private void updatePathType() { - if (ControlPoint.Type != PathType.PerfectCurve) + if (ControlPoint.Type != PathType.PERFECTCURVE) return; if (PointsInSegment.Count > 3) - ControlPoint.Type = PathType.Bezier; + ControlPoint.Type = PathType.BEZIER; if (PointsInSegment.Count != 3) return; @@ -233,7 +233,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components ReadOnlySpan points = PointsInSegment.Select(p => p.Position).ToArray(); RectangleF boundingBox = PathApproximator.CircularArcBoundingBox(points); if (boundingBox.Width >= 640 || boundingBox.Height >= 480) - ControlPoint.Type = PathType.Bezier; + ControlPoint.Type = PathType.BEZIER; } /// @@ -256,18 +256,22 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components private Color4 getColourFromNodeType() { - if (!(ControlPoint.Type is PathType pathType)) + if (ControlPoint.Type is not PathType pathType) return colours.Yellow; switch (pathType) { - case PathType.Catmull: + case { SplineType: SplineType.Catmull }: return colours.SeaFoam; - case PathType.Bezier: - return colours.Pink; + case { SplineType: SplineType.BSpline, Degree: null }: + return colours.PinkLighter; - case PathType.PerfectCurve: + case { SplineType: SplineType.BSpline, Degree: >= 1 }: + int idx = Math.Clamp(pathType.Degree.Value, 0, 3); + return new[] { colours.PinkDarker, colours.PinkDark, colours.Pink, colours.PinkLight }[idx]; + + case { SplineType: SplineType.PerfectCurve }: return colours.PurpleDark; default: diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs index f891d23bbd..b5c9016538 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs @@ -242,18 +242,15 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components { int indexInSegment = piece.PointsInSegment.IndexOf(piece.ControlPoint); - switch (type) + if (type.HasValue && type.Value.SplineType == SplineType.PerfectCurve) { - case PathType.PerfectCurve: - // Can't always create a circular arc out of 4 or more points, - // so we split the segment into one 3-point circular arc segment - // and one segment of the previous type. - int thirdPointIndex = indexInSegment + 2; + // Can't always create a circular arc out of 4 or more points, + // so we split the segment into one 3-point circular arc segment + // and one segment of the previous type. + int thirdPointIndex = indexInSegment + 2; - if (piece.PointsInSegment.Count > thirdPointIndex + 1) - piece.PointsInSegment[thirdPointIndex].Type = piece.PointsInSegment[0].Type; - - break; + if (piece.PointsInSegment.Count > thirdPointIndex + 1) + piece.PointsInSegment[thirdPointIndex].Type = piece.PointsInSegment[0].Type; } hitObject.Path.ExpectedDistance.Value = null; @@ -370,10 +367,11 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components curveTypeItems.Add(createMenuItemForPathType(null)); // todo: hide/disable items which aren't valid for selected points - curveTypeItems.Add(createMenuItemForPathType(PathType.Linear)); - curveTypeItems.Add(createMenuItemForPathType(PathType.PerfectCurve)); - curveTypeItems.Add(createMenuItemForPathType(PathType.Bezier)); - curveTypeItems.Add(createMenuItemForPathType(PathType.Catmull)); + curveTypeItems.Add(createMenuItemForPathType(PathType.LINEAR)); + curveTypeItems.Add(createMenuItemForPathType(PathType.PERFECTCURVE)); + curveTypeItems.Add(createMenuItemForPathType(PathType.BEZIER)); + curveTypeItems.Add(createMenuItemForPathType(PathType.BSpline(3))); + curveTypeItems.Add(createMenuItemForPathType(PathType.CATMULL)); var menuItems = new List { diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs index 9b6adc04cf..8f0a2ee781 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs @@ -51,7 +51,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders { RelativeSizeAxes = Axes.Both; - HitObject.Path.ControlPoints.Add(segmentStart = new PathControlPoint(Vector2.Zero, PathType.Linear)); + HitObject.Path.ControlPoints.Add(segmentStart = new PathControlPoint(Vector2.Zero, PathType.LINEAR)); currentSegmentLength = 1; } @@ -128,7 +128,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders Debug.Assert(lastPoint != null); segmentStart = lastPoint; - segmentStart.Type = PathType.Linear; + segmentStart.Type = PathType.LINEAR; currentSegmentLength = 1; } @@ -173,15 +173,15 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders { case 1: case 2: - segmentStart.Type = PathType.Linear; + segmentStart.Type = PathType.LINEAR; break; case 3: - segmentStart.Type = PathType.PerfectCurve; + segmentStart.Type = PathType.PERFECTCURVE; break; default: - segmentStart.Type = PathType.Bezier; + segmentStart.Type = PathType.BEZIER; break; } } @@ -195,7 +195,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders { HitObject.Path.ControlPoints.Add(cursor = new PathControlPoint { Position = Vector2.Zero }); - // The path type should be adjusted in the progression of updatePathType() (Linear -> PC -> Bezier). + // The path type should be adjusted in the progression of updatePathType() (LINEAR -> PC -> BEZIER). currentSegmentLength++; updatePathType(); } @@ -210,7 +210,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders HitObject.Path.ControlPoints.Remove(cursor); cursor = null; - // The path type should be adjusted in the reverse progression of updatePathType() (Bezier -> PC -> Linear). + // The path type should be adjusted in the reverse progression of updatePathType() (BEZIER -> PC -> LINEAR). currentSegmentLength--; updatePathType(); } diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs index e81941d254..b972f09136 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs @@ -320,7 +320,7 @@ namespace osu.Game.Rulesets.Osu.Edit if (mergedHitObject.Path.ControlPoints.Count == 0) { - mergedHitObject.Path.ControlPoints.Add(new PathControlPoint(Vector2.Zero, PathType.Linear)); + mergedHitObject.Path.ControlPoints.Add(new PathControlPoint(Vector2.Zero, PathType.LINEAR)); } // Merge all the selected hit objects into one slider path. @@ -350,7 +350,7 @@ namespace osu.Game.Rulesets.Osu.Edit // Turn the last control point into a linear type if this is the first merging circle in a sequence, so the subsequent control points can be inherited path type. if (!lastCircle) { - mergedHitObject.Path.ControlPoints.Last().Type = PathType.Linear; + mergedHitObject.Path.ControlPoints.Last().Type = PathType.LINEAR; } mergedHitObject.Path.ControlPoints.Add(new PathControlPoint(selectedMergeableObject.Position - mergedHitObject.Position)); diff --git a/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs b/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs index 5f47d486e6..2a76782a08 100644 --- a/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs +++ b/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs @@ -116,7 +116,7 @@ namespace osu.Game.Rulesets.Taiko.Objects double IHasDistance.Distance => Duration * Velocity; SliderPath IHasPath.Path - => new SliderPath(PathType.Linear, new[] { Vector2.Zero, new Vector2(1) }, ((IHasDistance)this).Distance / LegacyBeatmapEncoder.LEGACY_TAIKO_VELOCITY_MULTIPLIER); + => new SliderPath(PathType.LINEAR, new[] { Vector2.Zero, new Vector2(1) }, ((IHasDistance)this).Distance / LegacyBeatmapEncoder.LEGACY_TAIKO_VELOCITY_MULTIPLIER); #endregion } diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs index 66151a51e6..18c21046fb 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs @@ -663,7 +663,7 @@ namespace osu.Game.Tests.Beatmaps.Formats assertObjectHasBanks(hitObjects[9], HitSampleInfo.BANK_DRUM, HitSampleInfo.BANK_NORMAL); } - void assertObjectHasBanks(HitObject hitObject, string normalBank, string? additionsBank = null) + static void assertObjectHasBanks(HitObject hitObject, string normalBank, string? additionsBank = null) { Assert.AreEqual(normalBank, hitObject.Samples[0].Bank); @@ -808,14 +808,14 @@ namespace osu.Game.Tests.Beatmaps.Formats var first = ((IHasPath)decoded.HitObjects[0]).Path; Assert.That(first.ControlPoints[0].Position, Is.EqualTo(Vector2.Zero)); - Assert.That(first.ControlPoints[0].Type, Is.EqualTo(PathType.PerfectCurve)); + Assert.That(first.ControlPoints[0].Type, Is.EqualTo(PathType.PERFECTCURVE)); Assert.That(first.ControlPoints[1].Position, Is.EqualTo(new Vector2(161, -244))); Assert.That(first.ControlPoints[1].Type, Is.EqualTo(null)); // ReSharper disable once HeuristicUnreachableCode // weird one, see https://youtrack.jetbrains.com/issue/RIDER-70159. Assert.That(first.ControlPoints[2].Position, Is.EqualTo(new Vector2(376, -3))); - Assert.That(first.ControlPoints[2].Type, Is.EqualTo(PathType.Bezier)); + Assert.That(first.ControlPoints[2].Type, Is.EqualTo(PathType.BEZIER)); Assert.That(first.ControlPoints[3].Position, Is.EqualTo(new Vector2(68, 15))); Assert.That(first.ControlPoints[3].Type, Is.EqualTo(null)); Assert.That(first.ControlPoints[4].Position, Is.EqualTo(new Vector2(259, -132))); @@ -827,7 +827,7 @@ namespace osu.Game.Tests.Beatmaps.Formats var second = ((IHasPath)decoded.HitObjects[1]).Path; Assert.That(second.ControlPoints[0].Position, Is.EqualTo(Vector2.Zero)); - Assert.That(second.ControlPoints[0].Type, Is.EqualTo(PathType.PerfectCurve)); + Assert.That(second.ControlPoints[0].Type, Is.EqualTo(PathType.PERFECTCURVE)); Assert.That(second.ControlPoints[1].Position, Is.EqualTo(new Vector2(161, -244))); Assert.That(second.ControlPoints[1].Type, Is.EqualTo(null)); Assert.That(second.ControlPoints[2].Position, Is.EqualTo(new Vector2(376, -3))); @@ -837,14 +837,14 @@ namespace osu.Game.Tests.Beatmaps.Formats var third = ((IHasPath)decoded.HitObjects[2]).Path; Assert.That(third.ControlPoints[0].Position, Is.EqualTo(Vector2.Zero)); - Assert.That(third.ControlPoints[0].Type, Is.EqualTo(PathType.Bezier)); + Assert.That(third.ControlPoints[0].Type, Is.EqualTo(PathType.BEZIER)); Assert.That(third.ControlPoints[1].Position, Is.EqualTo(new Vector2(0, 192))); Assert.That(third.ControlPoints[1].Type, Is.EqualTo(null)); Assert.That(third.ControlPoints[2].Position, Is.EqualTo(new Vector2(224, 192))); Assert.That(third.ControlPoints[2].Type, Is.EqualTo(null)); Assert.That(third.ControlPoints[3].Position, Is.EqualTo(new Vector2(224, 0))); - Assert.That(third.ControlPoints[3].Type, Is.EqualTo(PathType.Bezier)); + Assert.That(third.ControlPoints[3].Type, Is.EqualTo(PathType.BEZIER)); Assert.That(third.ControlPoints[4].Position, Is.EqualTo(new Vector2(224, -192))); Assert.That(third.ControlPoints[4].Type, Is.EqualTo(null)); Assert.That(third.ControlPoints[5].Position, Is.EqualTo(new Vector2(480, -192))); @@ -856,7 +856,7 @@ namespace osu.Game.Tests.Beatmaps.Formats var fourth = ((IHasPath)decoded.HitObjects[3]).Path; Assert.That(fourth.ControlPoints[0].Position, Is.EqualTo(Vector2.Zero)); - Assert.That(fourth.ControlPoints[0].Type, Is.EqualTo(PathType.Bezier)); + Assert.That(fourth.ControlPoints[0].Type, Is.EqualTo(PathType.BEZIER)); Assert.That(fourth.ControlPoints[1].Position, Is.EqualTo(new Vector2(1, 1))); Assert.That(fourth.ControlPoints[1].Type, Is.EqualTo(null)); Assert.That(fourth.ControlPoints[2].Position, Is.EqualTo(new Vector2(2, 2))); @@ -870,7 +870,7 @@ namespace osu.Game.Tests.Beatmaps.Formats var fifth = ((IHasPath)decoded.HitObjects[4]).Path; Assert.That(fifth.ControlPoints[0].Position, Is.EqualTo(Vector2.Zero)); - Assert.That(fifth.ControlPoints[0].Type, Is.EqualTo(PathType.Bezier)); + Assert.That(fifth.ControlPoints[0].Type, Is.EqualTo(PathType.BEZIER)); Assert.That(fifth.ControlPoints[1].Position, Is.EqualTo(new Vector2(1, 1))); Assert.That(fifth.ControlPoints[1].Type, Is.EqualTo(null)); Assert.That(fifth.ControlPoints[2].Position, Is.EqualTo(new Vector2(2, 2))); @@ -881,7 +881,7 @@ namespace osu.Game.Tests.Beatmaps.Formats Assert.That(fifth.ControlPoints[4].Type, Is.EqualTo(null)); Assert.That(fifth.ControlPoints[5].Position, Is.EqualTo(new Vector2(4, 4))); - Assert.That(fifth.ControlPoints[5].Type, Is.EqualTo(PathType.Bezier)); + Assert.That(fifth.ControlPoints[5].Type, Is.EqualTo(PathType.BEZIER)); Assert.That(fifth.ControlPoints[6].Position, Is.EqualTo(new Vector2(5, 5))); Assert.That(fifth.ControlPoints[6].Type, Is.EqualTo(null)); @@ -889,12 +889,12 @@ namespace osu.Game.Tests.Beatmaps.Formats var sixth = ((IHasPath)decoded.HitObjects[5]).Path; Assert.That(sixth.ControlPoints[0].Position, Is.EqualTo(Vector2.Zero)); - Assert.That(sixth.ControlPoints[0].Type == PathType.Bezier); + Assert.That(sixth.ControlPoints[0].Type == PathType.BEZIER); Assert.That(sixth.ControlPoints[1].Position, Is.EqualTo(new Vector2(75, 145))); Assert.That(sixth.ControlPoints[1].Type == null); Assert.That(sixth.ControlPoints[2].Position, Is.EqualTo(new Vector2(170, 75))); - Assert.That(sixth.ControlPoints[2].Type == PathType.Bezier); + Assert.That(sixth.ControlPoints[2].Type == PathType.BEZIER); Assert.That(sixth.ControlPoints[3].Position, Is.EqualTo(new Vector2(300, 145))); Assert.That(sixth.ControlPoints[3].Type == null); Assert.That(sixth.ControlPoints[4].Position, Is.EqualTo(new Vector2(410, 20))); @@ -904,12 +904,12 @@ namespace osu.Game.Tests.Beatmaps.Formats var seventh = ((IHasPath)decoded.HitObjects[6]).Path; Assert.That(seventh.ControlPoints[0].Position, Is.EqualTo(Vector2.Zero)); - Assert.That(seventh.ControlPoints[0].Type == PathType.PerfectCurve); + Assert.That(seventh.ControlPoints[0].Type == PathType.PERFECTCURVE); Assert.That(seventh.ControlPoints[1].Position, Is.EqualTo(new Vector2(75, 145))); Assert.That(seventh.ControlPoints[1].Type == null); Assert.That(seventh.ControlPoints[2].Position, Is.EqualTo(new Vector2(170, 75))); - Assert.That(seventh.ControlPoints[2].Type == PathType.PerfectCurve); + Assert.That(seventh.ControlPoints[2].Type == PathType.PERFECTCURVE); Assert.That(seventh.ControlPoints[3].Position, Is.EqualTo(new Vector2(300, 145))); Assert.That(seventh.ControlPoints[3].Type == null); Assert.That(seventh.ControlPoints[4].Position, Is.EqualTo(new Vector2(410, 20))); @@ -1016,7 +1016,7 @@ namespace osu.Game.Tests.Beatmaps.Formats var controlPoints = ((IHasPath)decoded.HitObjects[0]).Path.ControlPoints; Assert.That(controlPoints.Count, Is.EqualTo(6)); - Assert.That(controlPoints.Single(c => c.Type != null).Type, Is.EqualTo(PathType.Catmull)); + Assert.That(controlPoints.Single(c => c.Type != null).Type, Is.EqualTo(PathType.CATMULL)); } } @@ -1032,9 +1032,9 @@ namespace osu.Game.Tests.Beatmaps.Formats var controlPoints = ((IHasPath)decoded.HitObjects[0]).Path.ControlPoints; Assert.That(controlPoints.Count, Is.EqualTo(4)); - Assert.That(controlPoints[0].Type, Is.EqualTo(PathType.Catmull)); - Assert.That(controlPoints[1].Type, Is.EqualTo(PathType.Catmull)); - Assert.That(controlPoints[2].Type, Is.EqualTo(PathType.Catmull)); + Assert.That(controlPoints[0].Type, Is.EqualTo(PathType.CATMULL)); + Assert.That(controlPoints[1].Type, Is.EqualTo(PathType.CATMULL)); + Assert.That(controlPoints[2].Type, Is.EqualTo(PathType.CATMULL)); Assert.That(controlPoints[3].Type, Is.Null); } } @@ -1051,7 +1051,7 @@ namespace osu.Game.Tests.Beatmaps.Formats var controlPoints = ((IHasPath)decoded.HitObjects[0]).Path.ControlPoints; Assert.That(controlPoints.Count, Is.EqualTo(4)); - Assert.That(controlPoints[0].Type, Is.EqualTo(PathType.Catmull)); + Assert.That(controlPoints[0].Type, Is.EqualTo(PathType.CATMULL)); Assert.That(controlPoints[0].Position, Is.EqualTo(Vector2.Zero)); Assert.That(controlPoints[1].Type, Is.Null); Assert.That(controlPoints[1].Position, Is.Not.EqualTo(Vector2.Zero)); diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs index 5d9049ead7..db50273f27 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs @@ -77,7 +77,7 @@ namespace osu.Game.Tests.Beatmaps.Formats compareBeatmaps(decoded, decodedAfterEncode); - ControlPointInfo removeLegacyControlPointTypes(ControlPointInfo controlPointInfo) + static ControlPointInfo removeLegacyControlPointTypes(ControlPointInfo controlPointInfo) { // emulate non-legacy control points by cloning the non-legacy portion. // the assertion is that the encoder can recreate this losslessly from hitobject data. @@ -125,10 +125,10 @@ namespace osu.Game.Tests.Beatmaps.Formats Position = new Vector2(0.6f), Path = new SliderPath(new[] { - new PathControlPoint(Vector2.Zero, PathType.Bezier), + new PathControlPoint(Vector2.Zero, PathType.BEZIER), new PathControlPoint(new Vector2(0.5f)), new PathControlPoint(new Vector2(0.51f)), // This is actually on the same position as the previous one in legacy beatmaps (truncated to int). - new PathControlPoint(new Vector2(1f), PathType.Bezier), + new PathControlPoint(new Vector2(1f), PathType.BEZIER), new PathControlPoint(new Vector2(2f)) }) }, diff --git a/osu.Game.Tests/Editing/LegacyEditorBeatmapPatcherTest.cs b/osu.Game.Tests/Editing/LegacyEditorBeatmapPatcherTest.cs index 5af0366e6e..21d8a165ff 100644 --- a/osu.Game.Tests/Editing/LegacyEditorBeatmapPatcherTest.cs +++ b/osu.Game.Tests/Editing/LegacyEditorBeatmapPatcherTest.cs @@ -162,7 +162,7 @@ namespace osu.Game.Tests.Editing { new PathControlPoint(Vector2.Zero), new PathControlPoint(Vector2.One), - new PathControlPoint(new Vector2(2), PathType.Bezier), + new PathControlPoint(new Vector2(2), PathType.BEZIER), new PathControlPoint(new Vector2(3)), }, 50) }, @@ -179,7 +179,7 @@ namespace osu.Game.Tests.Editing StartTime = 2000, Path = new SliderPath(new[] { - new PathControlPoint(Vector2.Zero, PathType.Bezier), + new PathControlPoint(Vector2.Zero, PathType.BEZIER), new PathControlPoint(new Vector2(4)), new PathControlPoint(new Vector2(5)), }, 100) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorClipboard.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorClipboard.cs index c4c05278b5..a766b253aa 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorClipboard.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorClipboard.cs @@ -72,7 +72,7 @@ namespace osu.Game.Tests.Visual.Editing ControlPoints = { new PathControlPoint(), - new PathControlPoint(new Vector2(100, 0), PathType.Bezier) + new PathControlPoint(new Vector2(100, 0), PathType.BEZIER) } } }; diff --git a/osu.Game.Tests/Visual/Editing/TestSceneHitObjectComposer.cs b/osu.Game.Tests/Visual/Editing/TestSceneHitObjectComposer.cs index ed3bffe5c2..f392841ac7 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneHitObjectComposer.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneHitObjectComposer.cs @@ -57,7 +57,7 @@ namespace osu.Game.Tests.Visual.Editing new Slider { Position = new Vector2(128, 256), - Path = new SliderPath(PathType.Linear, new[] + Path = new SliderPath(PathType.LINEAR, new[] { Vector2.Zero, new Vector2(216, 0), diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneBezierConverter.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneBezierConverter.cs index a40eab5948..5eb82ccbdc 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneBezierConverter.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneBezierConverter.cs @@ -114,23 +114,25 @@ namespace osu.Game.Tests.Visual.Gameplay { } - [TestCase(PathType.Linear)] - [TestCase(PathType.Bezier)] - [TestCase(PathType.Catmull)] - [TestCase(PathType.PerfectCurve)] - public void TestSingleSegment(PathType type) - => AddStep("create path", () => path.ControlPoints.AddRange(createSegment(type, Vector2.Zero, new Vector2(0, 100), new Vector2(100)))); + [TestCase(SplineType.Linear, null)] + [TestCase(SplineType.BSpline, null)] + [TestCase(SplineType.BSpline, 3)] + [TestCase(SplineType.Catmull, null)] + [TestCase(SplineType.PerfectCurve, null)] + public void TestSingleSegment(SplineType splineType, int? degree) + => AddStep("create path", () => path.ControlPoints.AddRange(createSegment(new PathType { SplineType = splineType, Degree = degree }, Vector2.Zero, new Vector2(0, 100), new Vector2(100)))); - [TestCase(PathType.Linear)] - [TestCase(PathType.Bezier)] - [TestCase(PathType.Catmull)] - [TestCase(PathType.PerfectCurve)] - public void TestMultipleSegment(PathType type) + [TestCase(SplineType.Linear, null)] + [TestCase(SplineType.BSpline, null)] + [TestCase(SplineType.BSpline, 3)] + [TestCase(SplineType.Catmull, null)] + [TestCase(SplineType.PerfectCurve, null)] + public void TestMultipleSegment(SplineType splineType, int? degree) { AddStep("create path", () => { - path.ControlPoints.AddRange(createSegment(PathType.Linear, Vector2.Zero)); - path.ControlPoints.AddRange(createSegment(type, new Vector2(0, 100), new Vector2(100), Vector2.Zero)); + path.ControlPoints.AddRange(createSegment(PathType.LINEAR, Vector2.Zero)); + path.ControlPoints.AddRange(createSegment(new PathType { SplineType = splineType, Degree = degree }, new Vector2(0, 100), new Vector2(100), Vector2.Zero)); }); } @@ -139,9 +141,9 @@ namespace osu.Game.Tests.Visual.Gameplay { AddStep("create path", () => { - path.ControlPoints.AddRange(createSegment(PathType.Linear, Vector2.Zero, new Vector2(100, 0))); - path.ControlPoints.AddRange(createSegment(PathType.Bezier, new Vector2(100, 0), new Vector2(150, 30), new Vector2(100, 100))); - path.ControlPoints.AddRange(createSegment(PathType.PerfectCurve, new Vector2(100, 100), new Vector2(25, 50), Vector2.Zero)); + path.ControlPoints.AddRange(createSegment(PathType.LINEAR, Vector2.Zero, new Vector2(100, 0))); + path.ControlPoints.AddRange(createSegment(PathType.BEZIER, new Vector2(100, 0), new Vector2(150, 30), new Vector2(100, 100))); + path.ControlPoints.AddRange(createSegment(PathType.PERFECTCURVE, new Vector2(100, 100), new Vector2(25, 50), Vector2.Zero)); }); } @@ -157,7 +159,7 @@ namespace osu.Game.Tests.Visual.Gameplay { AddStep("create path", () => { - path.ControlPoints.AddRange(createSegment(PathType.PerfectCurve, Vector2.Zero, new Vector2(width / 2, height), new Vector2(width, 0))); + path.ControlPoints.AddRange(createSegment(PathType.PERFECTCURVE, Vector2.Zero, new Vector2(width / 2, height), new Vector2(width, 0))); }); } @@ -170,11 +172,11 @@ namespace osu.Game.Tests.Visual.Gameplay switch (points) { case 2: - path.ControlPoints.AddRange(createSegment(PathType.PerfectCurve, Vector2.Zero, new Vector2(0, 100))); + path.ControlPoints.AddRange(createSegment(PathType.PERFECTCURVE, Vector2.Zero, new Vector2(0, 100))); break; case 4: - path.ControlPoints.AddRange(createSegment(PathType.PerfectCurve, Vector2.Zero, new Vector2(0, 100), new Vector2(100), new Vector2(100, 0))); + path.ControlPoints.AddRange(createSegment(PathType.PERFECTCURVE, Vector2.Zero, new Vector2(0, 100), new Vector2(100), new Vector2(100, 0))); break; } }); diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplaySampleTriggerSource.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplaySampleTriggerSource.cs index 0f16d3f394..3cbd5eefac 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplaySampleTriggerSource.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplaySampleTriggerSource.cs @@ -88,7 +88,7 @@ namespace osu.Game.Tests.Visual.Gameplay { HitWindows = new HitWindows(), StartTime = t += spacing, - Path = new SliderPath(PathType.Linear, new[] { Vector2.Zero, Vector2.UnitY * 200 }), + Path = new SliderPath(PathType.LINEAR, new[] { Vector2.Zero, Vector2.UnitY * 200 }), Samples = new[] { new HitSampleInfo(HitSampleInfo.HIT_WHISTLE, HitSampleInfo.BANK_SOFT) }, }, }); diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSliderPath.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSliderPath.cs index 635d9f9604..e4d99f6741 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSliderPath.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSliderPath.cs @@ -52,59 +52,68 @@ namespace osu.Game.Tests.Visual.Gameplay { } - [TestCase(PathType.Linear)] - [TestCase(PathType.Bezier)] - [TestCase(PathType.Catmull)] - [TestCase(PathType.PerfectCurve)] - public void TestSingleSegment(PathType type) - => AddStep("create path", () => path.ControlPoints.AddRange(createSegment(type, Vector2.Zero, new Vector2(0, 100), new Vector2(100)))); + [TestCase(SplineType.Linear, null)] + [TestCase(SplineType.BSpline, null)] + [TestCase(SplineType.BSpline, 3)] + [TestCase(SplineType.Catmull, null)] + [TestCase(SplineType.PerfectCurve, null)] + public void TestSingleSegment(SplineType splineType, int? degree) + => AddStep("create path", () => path.ControlPoints.AddRange(createSegment( + new PathType { SplineType = splineType, Degree = degree }, + Vector2.Zero, + new Vector2(0, 100), + new Vector2(100), + new Vector2(0, 200), + new Vector2(200) + ))); - [TestCase(PathType.Linear)] - [TestCase(PathType.Bezier)] - [TestCase(PathType.Catmull)] - [TestCase(PathType.PerfectCurve)] - public void TestMultipleSegment(PathType type) + [TestCase(SplineType.Linear, null)] + [TestCase(SplineType.BSpline, null)] + [TestCase(SplineType.BSpline, 3)] + [TestCase(SplineType.Catmull, null)] + [TestCase(SplineType.PerfectCurve, null)] + public void TestMultipleSegment(SplineType splineType, int? degree) { AddStep("create path", () => { - path.ControlPoints.AddRange(createSegment(PathType.Linear, Vector2.Zero)); - path.ControlPoints.AddRange(createSegment(type, new Vector2(0, 100), new Vector2(100), Vector2.Zero)); + path.ControlPoints.AddRange(createSegment(PathType.LINEAR, Vector2.Zero)); + path.ControlPoints.AddRange(createSegment(new PathType { SplineType = splineType, Degree = degree }, new Vector2(0, 100), new Vector2(100), Vector2.Zero)); }); } [Test] public void TestAddControlPoint() { - AddStep("create path", () => path.ControlPoints.AddRange(createSegment(PathType.Linear, Vector2.Zero, new Vector2(0, 100)))); + AddStep("create path", () => path.ControlPoints.AddRange(createSegment(PathType.LINEAR, Vector2.Zero, new Vector2(0, 100)))); AddStep("add point", () => path.ControlPoints.Add(new PathControlPoint { Position = new Vector2(100) })); } [Test] public void TestInsertControlPoint() { - AddStep("create path", () => path.ControlPoints.AddRange(createSegment(PathType.Linear, Vector2.Zero, new Vector2(100)))); + AddStep("create path", () => path.ControlPoints.AddRange(createSegment(PathType.LINEAR, Vector2.Zero, new Vector2(100)))); AddStep("insert point", () => path.ControlPoints.Insert(1, new PathControlPoint { Position = new Vector2(0, 100) })); } [Test] public void TestRemoveControlPoint() { - AddStep("create path", () => path.ControlPoints.AddRange(createSegment(PathType.Linear, Vector2.Zero, new Vector2(0, 100), new Vector2(100)))); + AddStep("create path", () => path.ControlPoints.AddRange(createSegment(PathType.LINEAR, Vector2.Zero, new Vector2(0, 100), new Vector2(100)))); AddStep("remove second point", () => path.ControlPoints.RemoveAt(1)); } [Test] public void TestChangePathType() { - AddStep("create path", () => path.ControlPoints.AddRange(createSegment(PathType.Linear, Vector2.Zero, new Vector2(0, 100), new Vector2(100)))); - AddStep("change type to bezier", () => path.ControlPoints[0].Type = PathType.Bezier); + AddStep("create path", () => path.ControlPoints.AddRange(createSegment(PathType.LINEAR, Vector2.Zero, new Vector2(0, 100), new Vector2(100)))); + AddStep("change type to bezier", () => path.ControlPoints[0].Type = PathType.BEZIER); } [Test] public void TestAddSegmentByChangingType() { - AddStep("create path", () => path.ControlPoints.AddRange(createSegment(PathType.Linear, Vector2.Zero, new Vector2(0, 100), new Vector2(100), new Vector2(100, 0)))); - AddStep("change second point type to bezier", () => path.ControlPoints[1].Type = PathType.Bezier); + AddStep("create path", () => path.ControlPoints.AddRange(createSegment(PathType.LINEAR, Vector2.Zero, new Vector2(0, 100), new Vector2(100), new Vector2(100, 0)))); + AddStep("change second point type to bezier", () => path.ControlPoints[1].Type = PathType.BEZIER); } [Test] @@ -112,8 +121,8 @@ namespace osu.Game.Tests.Visual.Gameplay { AddStep("create path", () => { - path.ControlPoints.AddRange(createSegment(PathType.Linear, Vector2.Zero, new Vector2(0, 100), new Vector2(100), new Vector2(100, 0))); - path.ControlPoints[1].Type = PathType.Bezier; + path.ControlPoints.AddRange(createSegment(PathType.LINEAR, Vector2.Zero, new Vector2(0, 100), new Vector2(100), new Vector2(100, 0))); + path.ControlPoints[1].Type = PathType.BEZIER; }); AddStep("change second point type to null", () => path.ControlPoints[1].Type = null); @@ -124,8 +133,8 @@ namespace osu.Game.Tests.Visual.Gameplay { AddStep("create path", () => { - path.ControlPoints.AddRange(createSegment(PathType.Linear, Vector2.Zero, new Vector2(0, 100), new Vector2(100), new Vector2(100, 0))); - path.ControlPoints[1].Type = PathType.Bezier; + path.ControlPoints.AddRange(createSegment(PathType.LINEAR, Vector2.Zero, new Vector2(0, 100), new Vector2(100), new Vector2(100, 0))); + path.ControlPoints[1].Type = PathType.BEZIER; }); AddStep("remove second point", () => path.ControlPoints.RemoveAt(1)); @@ -140,11 +149,11 @@ namespace osu.Game.Tests.Visual.Gameplay switch (points) { case 2: - path.ControlPoints.AddRange(createSegment(PathType.PerfectCurve, Vector2.Zero, new Vector2(0, 100))); + path.ControlPoints.AddRange(createSegment(PathType.PERFECTCURVE, Vector2.Zero, new Vector2(0, 100))); break; case 4: - path.ControlPoints.AddRange(createSegment(PathType.PerfectCurve, Vector2.Zero, new Vector2(0, 100), new Vector2(100), new Vector2(100, 0))); + path.ControlPoints.AddRange(createSegment(PathType.PERFECTCURVE, Vector2.Zero, new Vector2(0, 100), new Vector2(100), new Vector2(100, 0))); break; } }); @@ -153,35 +162,35 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestLengthenLastSegment() { - AddStep("create path", () => path.ControlPoints.AddRange(createSegment(PathType.Linear, Vector2.Zero, new Vector2(0, 100), new Vector2(100)))); + AddStep("create path", () => path.ControlPoints.AddRange(createSegment(PathType.LINEAR, Vector2.Zero, new Vector2(0, 100), new Vector2(100)))); AddStep("lengthen last segment", () => path.ExpectedDistance.Value = 300); } [Test] public void TestShortenLastSegment() { - AddStep("create path", () => path.ControlPoints.AddRange(createSegment(PathType.Linear, Vector2.Zero, new Vector2(0, 100), new Vector2(100)))); + AddStep("create path", () => path.ControlPoints.AddRange(createSegment(PathType.LINEAR, Vector2.Zero, new Vector2(0, 100), new Vector2(100)))); AddStep("shorten last segment", () => path.ExpectedDistance.Value = 150); } [Test] public void TestShortenFirstSegment() { - AddStep("create path", () => path.ControlPoints.AddRange(createSegment(PathType.Linear, Vector2.Zero, new Vector2(0, 100), new Vector2(100)))); + AddStep("create path", () => path.ControlPoints.AddRange(createSegment(PathType.LINEAR, Vector2.Zero, new Vector2(0, 100), new Vector2(100)))); AddStep("shorten first segment", () => path.ExpectedDistance.Value = 50); } [Test] public void TestShortenToZeroLength() { - AddStep("create path", () => path.ControlPoints.AddRange(createSegment(PathType.Linear, Vector2.Zero, new Vector2(0, 100), new Vector2(100)))); + AddStep("create path", () => path.ControlPoints.AddRange(createSegment(PathType.LINEAR, Vector2.Zero, new Vector2(0, 100), new Vector2(100)))); AddStep("shorten to 0 length", () => path.ExpectedDistance.Value = 0); } [Test] public void TestShortenToNegativeLength() { - AddStep("create path", () => path.ControlPoints.AddRange(createSegment(PathType.Linear, Vector2.Zero, new Vector2(0, 100), new Vector2(100)))); + AddStep("create path", () => path.ControlPoints.AddRange(createSegment(PathType.LINEAR, Vector2.Zero, new Vector2(0, 100), new Vector2(100)))); AddStep("shorten to -10 length", () => path.ExpectedDistance.Value = -10); } @@ -197,7 +206,7 @@ namespace osu.Game.Tests.Visual.Gameplay }; double[] distances = { 100d, 200d, 300d }; - AddStep("create path", () => path.ControlPoints.AddRange(positions.Select(p => new PathControlPoint(p, PathType.Linear)))); + AddStep("create path", () => path.ControlPoints.AddRange(positions.Select(p => new PathControlPoint(p, PathType.LINEAR)))); AddAssert("segment ends are correct", () => path.GetSegmentEnds(), () => Is.EqualTo(distances.Select(d => d / 300))); AddAssert("segment end positions recovered", () => path.GetSegmentEnds().Select(p => path.PositionAt(p)), () => Is.EqualTo(positions.Skip(1))); diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs index 4f8e935ee4..7029f61459 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; @@ -437,7 +438,7 @@ namespace osu.Game.Beatmaps.Formats // Explicit segments have a new format in which the type is injected into the middle of the control point string. // To preserve compatibility with osu-stable as much as possible, explicit segments with the same type are converted to use implicit segments by duplicating the control point. // One exception are consecutive perfect curves, which aren't supported in osu!stable and can lead to decoding issues if encoded as implicit segments - bool needsExplicitSegment = point.Type != lastType || point.Type == PathType.PerfectCurve; + bool needsExplicitSegment = point.Type != lastType || point.Type == PathType.PERFECTCURVE; // Another exception to this is when the last two control points of the last segment were duplicated. This is not a scenario supported by osu!stable. // Lazer does not add implicit segments for the last two control points of _any_ explicit segment, so an explicit segment is forced in order to maintain consistency with the decoder. @@ -455,19 +456,23 @@ namespace osu.Game.Beatmaps.Formats { switch (point.Type) { - case PathType.Bezier: + case { SplineType: SplineType.BSpline, Degree: > 0 }: + writer.Write($"B{point.Type.Value.Degree}|"); + break; + + case { SplineType: SplineType.BSpline, Degree: <= 0 }: writer.Write("B|"); break; - case PathType.Catmull: + case { SplineType: SplineType.Catmull }: writer.Write("C|"); break; - case PathType.PerfectCurve: + case { SplineType: SplineType.PerfectCurve }: writer.Write("P|"); break; - case PathType.Linear: + case { SplineType: SplineType.Linear }: writer.Write("L|"); break; } diff --git a/osu.Game/Database/LegacyBeatmapExporter.cs b/osu.Game/Database/LegacyBeatmapExporter.cs index ece705f685..9ca12a79dd 100644 --- a/osu.Game/Database/LegacyBeatmapExporter.cs +++ b/osu.Game/Database/LegacyBeatmapExporter.cs @@ -78,10 +78,10 @@ namespace osu.Game.Database // wherein the last control point of an otherwise-single-segment slider path has a different type than previous, // which would lead to sliders being mangled when exported back to stable. // normally, that would be handled by the `BezierConverter.ConvertToModernBezier()` call below, - // which outputs a slider path containing only Bezier control points, + // which outputs a slider path containing only BEZIER control points, // but a non-inherited last control point is (rightly) not considered to be starting a new segment, // therefore it would fail to clear the `CountSegments() <= 1` check. - // by clearing explicitly we both fix the issue and avoid unnecessary conversions to Bezier. + // by clearing explicitly we both fix the issue and avoid unnecessary conversions to BEZIER. if (hasPath.Path.ControlPoints.Count > 1) hasPath.Path.ControlPoints[^1].Type = null; diff --git a/osu.Game/Rulesets/Objects/BezierConverter.cs b/osu.Game/Rulesets/Objects/BezierConverter.cs index 0c878fa1fd..74fbe7d8f9 100644 --- a/osu.Game/Rulesets/Objects/BezierConverter.cs +++ b/osu.Game/Rulesets/Objects/BezierConverter.cs @@ -68,26 +68,26 @@ namespace osu.Game.Rulesets.Objects // The current vertex ends the segment var segmentVertices = vertices.AsSpan().Slice(start, i - start + 1); - var segmentType = controlPoints[start].Type ?? PathType.Linear; + var segmentType = controlPoints[start].Type ?? PathType.LINEAR; switch (segmentType) { - case PathType.Catmull: + case { SplineType: SplineType.Catmull }: result.AddRange(from segment in ConvertCatmullToBezierAnchors(segmentVertices) from v in segment select v + position); - break; - case PathType.Linear: + case { SplineType: SplineType.Linear }: result.AddRange(from segment in ConvertLinearToBezierAnchors(segmentVertices) from v in segment select v + position); - break; - case PathType.PerfectCurve: + case { SplineType: SplineType.PerfectCurve }: result.AddRange(ConvertCircleToBezierAnchors(segmentVertices).Select(v => v + position)); - break; default: + if (segmentType.Degree != null) + throw new NotImplementedException("BSpline conversion of arbitrary degree is not implemented."); + foreach (Vector2 v in segmentVertices) { result.Add(v + position); @@ -104,7 +104,7 @@ namespace osu.Game.Rulesets.Objects } /// - /// Converts a path of control points to an identical path using only Bezier type control points. + /// Converts a path of control points to an identical path using only BEZIER type control points. /// /// The control points of the path. /// The list of bezier control points. @@ -124,38 +124,38 @@ namespace osu.Game.Rulesets.Objects // The current vertex ends the segment var segmentVertices = vertices.AsSpan().Slice(start, i - start + 1); - var segmentType = controlPoints[start].Type ?? PathType.Linear; + var segmentType = controlPoints[start].Type ?? PathType.LINEAR; switch (segmentType) { - case PathType.Catmull: + case { SplineType: SplineType.Catmull }: foreach (var segment in ConvertCatmullToBezierAnchors(segmentVertices)) { for (int j = 0; j < segment.Length - 1; j++) { - result.Add(new PathControlPoint(segment[j], j == 0 ? PathType.Bezier : null)); + result.Add(new PathControlPoint(segment[j], j == 0 ? PathType.BEZIER : null)); } } break; - case PathType.Linear: + case { SplineType: SplineType.Linear }: foreach (var segment in ConvertLinearToBezierAnchors(segmentVertices)) { for (int j = 0; j < segment.Length - 1; j++) { - result.Add(new PathControlPoint(segment[j], j == 0 ? PathType.Bezier : null)); + result.Add(new PathControlPoint(segment[j], j == 0 ? PathType.BEZIER : null)); } } break; - case PathType.PerfectCurve: + case { SplineType: SplineType.PerfectCurve }: var circleResult = ConvertCircleToBezierAnchors(segmentVertices); for (int j = 0; j < circleResult.Length - 1; j++) { - result.Add(new PathControlPoint(circleResult[j], j == 0 ? PathType.Bezier : null)); + result.Add(new PathControlPoint(circleResult[j], j == 0 ? PathType.BEZIER : null)); } break; @@ -163,7 +163,7 @@ namespace osu.Game.Rulesets.Objects default: for (int j = 0; j < segmentVertices.Length - 1; j++) { - result.Add(new PathControlPoint(segmentVertices[j], j == 0 ? PathType.Bezier : null)); + result.Add(new PathControlPoint(segmentVertices[j], j == 0 ? segmentType : null)); } break; diff --git a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs index d20f2d31bb..30f4c092d9 100644 --- a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs @@ -224,16 +224,18 @@ namespace osu.Game.Rulesets.Objects.Legacy { default: case 'C': - return PathType.Catmull; + return new PathType(SplineType.Catmull); case 'B': - return PathType.Bezier; + if (input.Length > 1 && int.TryParse(input.Substring(1), out int degree) && degree > 0) + return new PathType { SplineType = SplineType.BSpline, Degree = degree }; + return new PathType(SplineType.BSpline); case 'L': - return PathType.Linear; + return new PathType(SplineType.Linear); case 'P': - return PathType.PerfectCurve; + return new PathType(SplineType.PerfectCurve); } } @@ -320,14 +322,14 @@ namespace osu.Game.Rulesets.Objects.Legacy readPoint(endPoint, offset, out vertices[^1]); // Edge-case rules (to match stable). - if (type == PathType.PerfectCurve) + if (type == PathType.PERFECTCURVE) { if (vertices.Length != 3) - type = PathType.Bezier; + type = PathType.BEZIER; else if (isLinear(vertices)) { // osu-stable special-cased colinear perfect curves to a linear path - type = PathType.Linear; + type = PathType.LINEAR; } } @@ -349,10 +351,10 @@ namespace osu.Game.Rulesets.Objects.Legacy if (vertices[endIndex].Position != vertices[endIndex - 1].Position) continue; - // Legacy Catmull sliders don't support multiple segments, so adjacent Catmull segments should be treated as a single one. + // Legacy CATMULL sliders don't support multiple segments, so adjacent CATMULL segments should be treated as a single one. // Importantly, this is not applied to the first control point, which may duplicate the slider path's position // resulting in a duplicate (0,0) control point in the resultant list. - if (type == PathType.Catmull && endIndex > 1 && FormatVersion < LegacyBeatmapEncoder.FIRST_LAZER_VERSION) + if (type == PathType.CATMULL && endIndex > 1 && FormatVersion < LegacyBeatmapEncoder.FIRST_LAZER_VERSION) continue; // The last control point of each segment is not allowed to start a new implicit segment. diff --git a/osu.Game/Rulesets/Objects/SliderPath.cs b/osu.Game/Rulesets/Objects/SliderPath.cs index 0ac057578b..4c24c111be 100644 --- a/osu.Game/Rulesets/Objects/SliderPath.cs +++ b/osu.Game/Rulesets/Objects/SliderPath.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.Collections.Specialized; using System.Diagnostics; using System.Linq; +using Microsoft.Toolkit.HighPerformance; using Newtonsoft.Json; using osu.Framework.Bindables; using osu.Framework.Caching; @@ -260,7 +261,7 @@ namespace osu.Game.Rulesets.Objects // The current vertex ends the segment var segmentVertices = vertices.AsSpan().Slice(start, i - start + 1); - var segmentType = ControlPoints[start].Type ?? PathType.Linear; + var segmentType = ControlPoints[start].Type ?? PathType.LINEAR; // No need to calculate path when there is only 1 vertex if (segmentVertices.Length == 1) @@ -288,12 +289,12 @@ namespace osu.Game.Rulesets.Objects private List calculateSubPath(ReadOnlySpan subControlPoints, PathType type) { - switch (type) + switch (type.SplineType) { - case PathType.Linear: + case SplineType.Linear: return PathApproximator.ApproximateLinear(subControlPoints); - case PathType.PerfectCurve: + case SplineType.PerfectCurve: if (subControlPoints.Length != 3) break; @@ -305,11 +306,11 @@ namespace osu.Game.Rulesets.Objects return subPath; - case PathType.Catmull: + case SplineType.Catmull: return PathApproximator.ApproximateCatmull(subControlPoints); } - return PathApproximator.ApproximateBezier(subControlPoints); + return PathApproximator.ApproximateBSpline(subControlPoints, type.Degree ?? subControlPoints.Length); } private void calculateLength() diff --git a/osu.Game/Rulesets/Objects/SliderPathExtensions.cs b/osu.Game/Rulesets/Objects/SliderPathExtensions.cs index 6c88f01249..d7e5e4574d 100644 --- a/osu.Game/Rulesets/Objects/SliderPathExtensions.cs +++ b/osu.Game/Rulesets/Objects/SliderPathExtensions.cs @@ -29,11 +29,11 @@ namespace osu.Game.Rulesets.Objects { var controlPoints = sliderPath.ControlPoints; - var inheritedLinearPoints = controlPoints.Where(p => sliderPath.PointsInSegment(p)[0].Type == PathType.Linear && p.Type is null).ToList(); + var inheritedLinearPoints = controlPoints.Where(p => sliderPath.PointsInSegment(p)[0].Type == PathType.LINEAR && p.Type is null).ToList(); // Inherited points after a linear point, as well as the first control point if it inherited, // should be treated as linear points, so their types are temporarily changed to linear. - inheritedLinearPoints.ForEach(p => p.Type = PathType.Linear); + inheritedLinearPoints.ForEach(p => p.Type = PathType.LINEAR); double[] segmentEnds = sliderPath.GetSegmentEnds().ToArray(); @@ -53,7 +53,7 @@ namespace osu.Game.Rulesets.Objects inheritedLinearPoints.ForEach(p => p.Type = null); // Recalculate middle perfect curve control points at the end of the slider path. - if (controlPoints.Count >= 3 && controlPoints[^3].Type == PathType.PerfectCurve && controlPoints[^2].Type is null && segmentEnds.Any()) + if (controlPoints.Count >= 3 && controlPoints[^3].Type == PathType.PERFECTCURVE && controlPoints[^2].Type is null && segmentEnds.Any()) { double lastSegmentStart = segmentEnds.Length > 1 ? segmentEnds[^2] : 0; double lastSegmentEnd = segmentEnds[^1]; diff --git a/osu.Game/Rulesets/Objects/Types/PathType.cs b/osu.Game/Rulesets/Objects/Types/PathType.cs index 923ce9eba4..41472fd8b5 100644 --- a/osu.Game/Rulesets/Objects/Types/PathType.cs +++ b/osu.Game/Rulesets/Objects/Types/PathType.cs @@ -1,13 +1,59 @@ // 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.Diagnostics; + namespace osu.Game.Rulesets.Objects.Types { - public enum PathType + public enum SplineType { Catmull, - Bezier, + BSpline, Linear, PerfectCurve } + + public struct PathType + { + public static readonly PathType CATMULL = new PathType(SplineType.Catmull); + public static readonly PathType BEZIER = new PathType(SplineType.BSpline); + public static readonly PathType LINEAR = new PathType(SplineType.Linear); + public static readonly PathType PERFECTCURVE = new PathType(SplineType.PerfectCurve); + + /// + /// The type of the spline that should be used to interpret the control points of the path. + /// + public SplineType SplineType { get; init; } + + /// + /// The degree of a BSpline. Unused if is not . + /// Null means the degree is equal to the number of control points, 1 means linear, 2 means quadratic, etc. + /// + public int? Degree { get; init; } + + public PathType(SplineType splineType) + { + SplineType = splineType; + Degree = null; + } + + public override int GetHashCode() + => HashCode.Combine(SplineType, Degree); + + public override bool Equals(object? obj) + => obj is PathType pathType && this == pathType; + + public static bool operator ==(PathType a, PathType b) + => a.SplineType == b.SplineType && a.Degree == b.Degree; + + public static bool operator !=(PathType a, PathType b) + => a.SplineType != b.SplineType || a.Degree != b.Degree; + + public static PathType BSpline(int degree) + { + Debug.Assert(degree > 0); + return new PathType { SplineType = SplineType.BSpline, Degree = degree }; + } + } } diff --git a/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs b/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs index 793d43f7ef..a114529bf9 100644 --- a/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs +++ b/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs @@ -246,13 +246,13 @@ namespace osu.Game.Screens.Play.HUD barPath = new SliderPath(new[] { - new PathControlPoint(new Vector2(0, 0), PathType.Linear), - new PathControlPoint(new Vector2(curveStart - curve_smoothness, 0), PathType.Bezier), + new PathControlPoint(new Vector2(0, 0), PathType.LINEAR), + new PathControlPoint(new Vector2(curveStart - curve_smoothness, 0), PathType.BEZIER), new PathControlPoint(new Vector2(curveStart, 0)), - new PathControlPoint(new Vector2(curveStart, 0) + diagonalDir * curve_smoothness, PathType.Linear), - new PathControlPoint(new Vector2(curveEnd, BarHeight.Value) - diagonalDir * curve_smoothness, PathType.Bezier), + new PathControlPoint(new Vector2(curveStart, 0) + diagonalDir * curve_smoothness, PathType.LINEAR), + new PathControlPoint(new Vector2(curveEnd, BarHeight.Value) - diagonalDir * curve_smoothness, PathType.BEZIER), new PathControlPoint(new Vector2(curveEnd, BarHeight.Value)), - new PathControlPoint(new Vector2(curveEnd + curve_smoothness, BarHeight.Value), PathType.Linear), + new PathControlPoint(new Vector2(curveEnd + curve_smoothness, BarHeight.Value), PathType.LINEAR), new PathControlPoint(new Vector2(barLength, BarHeight.Value)), }); From 3f85aa79c5b4402b714da14295b6b796ffa8d6e6 Mon Sep 17 00:00:00 2001 From: cs Date: Sat, 11 Nov 2023 10:45:22 +0100 Subject: [PATCH 16/91] Add free-hand drawing of sliders to the editor --- .../Sliders/SliderPlacementBlueprint.cs | 86 +++++++++++++++++-- .../Edit/ISliderDrawingSettingsProvider.cs | 12 +++ .../Edit/OsuHitObjectComposer.cs | 8 +- .../Edit/OsuSliderDrawingSettingsProvider.cs | 68 +++++++++++++++ osu.Game/Rulesets/Objects/SliderPath.cs | 8 +- 5 files changed, 171 insertions(+), 11 deletions(-) create mode 100644 osu.Game.Rulesets.Osu/Edit/ISliderDrawingSettingsProvider.cs create mode 100644 osu.Game.Rulesets.Osu/Edit/OsuSliderDrawingSettingsProvider.cs diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs index 8f0a2ee781..a5c6ae9465 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs @@ -10,6 +10,7 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Input; using osu.Framework.Input.Events; +using osu.Framework.Utils; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Types; @@ -44,6 +45,11 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders [Resolved(CanBeNull = true)] private IDistanceSnapProvider distanceSnapProvider { get; set; } + [Resolved(CanBeNull = true)] + private ISliderDrawingSettingsProvider drawingSettingsProvider { get; set; } + + private readonly IncrementalBSplineBuilder bSplineBuilder = new IncrementalBSplineBuilder(); + protected override bool IsValidForPlacement => HitObject.Path.HasValidLength; public SliderPlacementBlueprint() @@ -73,6 +79,13 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders { base.LoadComplete(); inputManager = GetContainingInputManager(); + + drawingSettingsProvider.Tolerance.BindValueChanged(e => + { + if (bSplineBuilder.Tolerance != e.NewValue) + bSplineBuilder.Tolerance = e.NewValue; + updateSliderPathFromBSplineBuilder(); + }, true); } [Resolved] @@ -98,7 +111,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders ApplyDefaultsToHitObject(); break; - case SliderPlacementState.Body: + case SliderPlacementState.ControlPoints: updateCursor(); break; } @@ -115,7 +128,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders beginCurve(); break; - case SliderPlacementState.Body: + case SliderPlacementState.ControlPoints: if (canPlaceNewControlPoint(out var lastPoint)) { // Place a new point by detatching the current cursor. @@ -139,9 +152,62 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders return true; } + protected override bool OnDragStart(DragStartEvent e) + { + if (e.Button == MouseButton.Left) + { + switch (state) + { + case SliderPlacementState.Initial: + return true; + + case SliderPlacementState.ControlPoints: + if (HitObject.Path.ControlPoints.Count < 3) + { + var lastCp = HitObject.Path.ControlPoints.LastOrDefault(); + if (lastCp != cursor) + return false; + + bSplineBuilder.Clear(); + bSplineBuilder.AddLinearPoint(ToLocalSpace(e.ScreenSpaceMousePosition) - HitObject.Position); + setState(SliderPlacementState.Drawing); + return true; + } + return false; + } + } + return base.OnDragStart(e); + } + + protected override void OnDrag(DragEvent e) + { + base.OnDrag(e); + + bSplineBuilder.AddLinearPoint(ToLocalSpace(e.ScreenSpaceMousePosition) - HitObject.Position); + updateSliderPathFromBSplineBuilder(); + } + + private void updateSliderPathFromBSplineBuilder() + { + Scheduler.AddOnce(static self => + { + var cps = self.bSplineBuilder.GetControlPoints(); + self.HitObject.Path.ControlPoints.RemoveRange(1, self.HitObject.Path.ControlPoints.Count - 1); + self.HitObject.Path.ControlPoints.AddRange(cps.Select(v => new PathControlPoint(v))); + }, this); + } + + protected override void OnDragEnd(DragEndEvent e) + { + base.OnDragEnd(e); + + if (state == SliderPlacementState.Drawing) + endCurve(); + } + protected override void OnMouseUp(MouseUpEvent e) { - if (state == SliderPlacementState.Body && e.Button == MouseButton.Right) + if (state == SliderPlacementState.ControlPoints && e.Button == MouseButton.Right) endCurve(); base.OnMouseUp(e); } @@ -149,7 +215,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders private void beginCurve() { BeginPlacement(commitStart: true); - setState(SliderPlacementState.Body); + setState(SliderPlacementState.ControlPoints); } private void endCurve() @@ -169,6 +235,12 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders private void updatePathType() { + if (state == SliderPlacementState.Drawing) + { + segmentStart.Type = PathType.BSpline(3); + return; + } + switch (currentSegmentLength) { case 1: @@ -201,7 +273,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders } // Update the cursor position. - var result = positionSnapProvider?.FindSnappedPositionAndTime(inputManager.CurrentState.Mouse.Position, state == SliderPlacementState.Body ? SnapType.GlobalGrids : SnapType.All); + var result = positionSnapProvider?.FindSnappedPositionAndTime(inputManager.CurrentState.Mouse.Position, state == SliderPlacementState.ControlPoints ? SnapType.GlobalGrids : SnapType.All); cursor.Position = ToLocalSpace(result?.ScreenSpacePosition ?? inputManager.CurrentState.Mouse.Position) - HitObject.Position; } else if (cursor != null) @@ -248,7 +320,9 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders private enum SliderPlacementState { Initial, - Body, + ControlPoints, + Drawing, + DrawingFinalization } } } diff --git a/osu.Game.Rulesets.Osu/Edit/ISliderDrawingSettingsProvider.cs b/osu.Game.Rulesets.Osu/Edit/ISliderDrawingSettingsProvider.cs new file mode 100644 index 0000000000..1138588259 --- /dev/null +++ b/osu.Game.Rulesets.Osu/Edit/ISliderDrawingSettingsProvider.cs @@ -0,0 +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 osu.Framework.Bindables; + +namespace osu.Game.Rulesets.Osu.Edit +{ + public interface ISliderDrawingSettingsProvider + { + BindableFloat Tolerance { get; } + } +} diff --git a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs index 0f8c960b65..d958b558cf 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs @@ -63,6 +63,9 @@ namespace osu.Game.Rulesets.Osu.Edit [Cached(typeof(IDistanceSnapProvider))] protected readonly OsuDistanceSnapProvider DistanceSnapProvider = new OsuDistanceSnapProvider(); + [Cached(typeof(ISliderDrawingSettingsProvider))] + protected readonly OsuSliderDrawingSettingsProvider SliderDrawingSettingsProvider = new OsuSliderDrawingSettingsProvider(); + [BackgroundDependencyLoader] private void load() { @@ -96,8 +99,11 @@ namespace osu.Game.Rulesets.Osu.Edit RightToolbox.Add(new TransformToolboxGroup { - RotationHandler = BlueprintContainer.SelectionHandler.RotationHandler + RotationHandler = BlueprintContainer.SelectionHandler.RotationHandler, }); + + AddInternal(SliderDrawingSettingsProvider); + SliderDrawingSettingsProvider.AttachToToolbox(RightToolbox); } protected override ComposeBlueprintContainer CreateBlueprintContainer() diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSliderDrawingSettingsProvider.cs b/osu.Game.Rulesets.Osu/Edit/OsuSliderDrawingSettingsProvider.cs new file mode 100644 index 0000000000..ba2c39e1b5 --- /dev/null +++ b/osu.Game.Rulesets.Osu/Edit/OsuSliderDrawingSettingsProvider.cs @@ -0,0 +1,68 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Utils; +using osu.Game.Graphics.UserInterface; +using osu.Game.Rulesets.Edit; + +namespace osu.Game.Rulesets.Osu.Edit +{ + public partial class OsuSliderDrawingSettingsProvider : Drawable, ISliderDrawingSettingsProvider + { + public BindableFloat Tolerance { get; } = new BindableFloat(0.1f) + { + MinValue = 0.05f, + MaxValue = 1f, + Precision = 0.01f + }; + + private BindableInt sliderTolerance = new BindableInt(10) + { + MinValue = 5, + MaxValue = 100 + }; + + private ExpandableSlider toleranceSlider = null!; + + private EditorToolboxGroup? toolboxGroup; + + public OsuSliderDrawingSettingsProvider() + { + sliderTolerance.BindValueChanged(v => + { + float newValue = v.NewValue / 100f; + if (!Precision.AlmostEquals(newValue, Tolerance.Value, 1e-7f)) + Tolerance.Value = newValue; + }); + Tolerance.BindValueChanged(v => + { + int newValue = (int)Math.Round(v.NewValue * 100f); + if (sliderTolerance.Value != newValue) + sliderTolerance.Value = newValue; + }); + } + + public void AttachToToolbox(ExpandingToolboxContainer toolboxContainer) + { + toolboxContainer.Add(toolboxGroup = new EditorToolboxGroup("drawing") + { + Children = new Drawable[] + { + toleranceSlider = new ExpandableSlider + { + Current = sliderTolerance + } + } + }); + + sliderTolerance.BindValueChanged(e => + { + toleranceSlider.ContractedLabelText = $"Tolerance: {e.NewValue:N0}"; + toleranceSlider.ExpandedLabelText = $"Tolerance: {e.NewValue:N0}"; + }, true); + } + } +} diff --git a/osu.Game/Rulesets/Objects/SliderPath.cs b/osu.Game/Rulesets/Objects/SliderPath.cs index 4c24c111be..75f1ab868d 100644 --- a/osu.Game/Rulesets/Objects/SliderPath.cs +++ b/osu.Game/Rulesets/Objects/SliderPath.cs @@ -292,13 +292,13 @@ namespace osu.Game.Rulesets.Objects switch (type.SplineType) { case SplineType.Linear: - return PathApproximator.ApproximateLinear(subControlPoints); + return PathApproximator.LinearToPiecewiseLinear(subControlPoints); case SplineType.PerfectCurve: if (subControlPoints.Length != 3) break; - List subPath = PathApproximator.ApproximateCircularArc(subControlPoints); + List subPath = PathApproximator.CircularArcToPiecewiseLinear(subControlPoints); // If for some reason a circular arc could not be fit to the 3 given points, fall back to a numerically stable bezier approximation. if (subPath.Count == 0) @@ -307,10 +307,10 @@ namespace osu.Game.Rulesets.Objects return subPath; case SplineType.Catmull: - return PathApproximator.ApproximateCatmull(subControlPoints); + return PathApproximator.CatmullToPiecewiseLinear(subControlPoints); } - return PathApproximator.ApproximateBSpline(subControlPoints, type.Degree ?? subControlPoints.Length); + return PathApproximator.BSplineToPiecewiseLinear(subControlPoints, type.Degree ?? subControlPoints.Length); } private void calculateLength() From 4e1e19728cb8ff9e11f18ab9c0e8635c2cc2ba9a Mon Sep 17 00:00:00 2001 From: ratinfx Date: Sat, 11 Nov 2023 14:02:42 +0100 Subject: [PATCH 17/91] Refactor HitObject selection in Composer --- .../Edit/ManiaHitObjectComposer.cs | 43 ++++++++++++++----- .../Edit/OsuHitObjectComposer.cs | 36 +++++++++++++--- .../Rulesets/Edit/EditorTimestampParser.cs | 40 +---------------- osu.Game/Rulesets/Edit/HitObjectComposer.cs | 14 ++---- osu.Game/Screens/Edit/Editor.cs | 11 +---- 5 files changed, 68 insertions(+), 76 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs b/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs index d217f04651..b04d3f895d 100644 --- a/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs +++ b/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs @@ -3,16 +3,15 @@ #nullable disable -using System; using System.Collections.Generic; using System.Linq; +using System.Text.RegularExpressions; using osu.Game.Beatmaps; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit.Tools; using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mania.UI; using osu.Game.Rulesets.Mods; -using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Screens.Edit.Compose.Components; @@ -50,22 +49,44 @@ namespace osu.Game.Rulesets.Mania.Edit new HoldNoteCompositionTool() }; + private static readonly Regex selection_regex = new Regex(@"^\d+\|\d+(,\d+\|\d+)*$"); + public override string ConvertSelectionToString() - => string.Join(ObjectSeparator, EditorBeatmap.SelectedHitObjects.Cast().OrderBy(h => h.StartTime).Select(h => $"{h.StartTime}|{h.Column}")); + => string.Join(',', EditorBeatmap.SelectedHitObjects.Cast().OrderBy(h => h.StartTime).Select(h => $"{h.StartTime}|{h.Column}")); - public override bool HandleHitObjectSelection(HitObject hitObject, string objectInfo) + public override void SelectHitObjects(double timestamp, string objectDescription) { - if (hitObject is not ManiaHitObject maniaHitObject) - return false; + if (!selection_regex.IsMatch(objectDescription)) + return; - double[] split = objectInfo.Split('|').Select(double.Parse).ToArray(); + List remainingHitObjects = EditorBeatmap.HitObjects.Cast().Where(h => h.StartTime >= timestamp).ToList(); + string[] split = objectDescription.Split(',').ToArray(); + + for (int i = 0; i < split.Length; i++) + { + ManiaHitObject current = remainingHitObjects.FirstOrDefault(h => shouldBeSelected(h, split[i])); + + if (current == null) + continue; + + EditorBeatmap.SelectedHitObjects.Add(current); + + if (i < split.Length - 1) + remainingHitObjects = remainingHitObjects.Where(h => h != current && h.StartTime >= current.StartTime).ToList(); + } + } + + private bool shouldBeSelected(ManiaHitObject hitObject, string objectInfo) + { + string[] split = objectInfo.Split('|').ToArray(); if (split.Length != 2) return false; - double timeValue = split[0]; - double columnValue = split[1]; - return Math.Abs(maniaHitObject.StartTime - timeValue) < 0.5 - && Math.Abs(maniaHitObject.Column - columnValue) < 0.5; + if (!double.TryParse(split[0], out double time) || !int.TryParse(split[1], out int column)) + return false; + + return hitObject.StartTime == time + && hitObject.Column == column; } } } diff --git a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs index 0c63cf71d8..e9c222b0e7 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs @@ -6,6 +6,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Text.RegularExpressions; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Caching; @@ -103,18 +104,39 @@ namespace osu.Game.Rulesets.Osu.Edit protected override ComposeBlueprintContainer CreateBlueprintContainer() => new OsuBlueprintContainer(this); + private static readonly Regex selection_regex = new Regex(@"^\d+(,\d+)*$"); + public override string ConvertSelectionToString() - => string.Join(ObjectSeparator, selectedHitObjects.Cast().OrderBy(h => h.StartTime).Select(h => (h.IndexInCurrentCombo + 1).ToString())); + => string.Join(',', selectedHitObjects.Cast().OrderBy(h => h.StartTime).Select(h => (h.IndexInCurrentCombo + 1).ToString())); - public override bool HandleHitObjectSelection(HitObject hitObject, string objectInfo) + public override void SelectHitObjects(double timestamp, string objectDescription) { - if (hitObject is not OsuHitObject osuHitObject) + if (!selection_regex.IsMatch(objectDescription)) + return; + + List remainingHitObjects = EditorBeatmap.HitObjects.Cast().Where(h => h.StartTime >= timestamp).ToList(); + string[] split = objectDescription.Split(',').ToArray(); + + for (int i = 0; i < split.Length; i++) + { + OsuHitObject current = remainingHitObjects.FirstOrDefault(h => shouldBeSelected(h, split[i])); + + if (current == null) + continue; + + EditorBeatmap.SelectedHitObjects.Add(current); + + if (i < split.Length - 1) + remainingHitObjects = remainingHitObjects.Where(h => h != current && h.StartTime >= current.StartTime).ToList(); + } + } + + private bool shouldBeSelected(OsuHitObject hitObject, string objectInfo) + { + if (!int.TryParse(objectInfo, out int combo) || combo < 1) return false; - if (!int.TryParse(objectInfo, out int comboValue) || comboValue < 1) - return false; - - return osuHitObject.IndexInCurrentCombo + 1 == comboValue; + return hitObject.IndexInCurrentCombo + 1 == combo; } private DistanceSnapGrid distanceSnapGrid; diff --git a/osu.Game/Rulesets/Edit/EditorTimestampParser.cs b/osu.Game/Rulesets/Edit/EditorTimestampParser.cs index 4e5a696102..7d4247b269 100644 --- a/osu.Game/Rulesets/Edit/EditorTimestampParser.cs +++ b/osu.Game/Rulesets/Edit/EditorTimestampParser.cs @@ -2,11 +2,9 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Text.RegularExpressions; -using osu.Game.Rulesets.Objects; namespace osu.Game.Rulesets.Edit { @@ -31,43 +29,7 @@ namespace osu.Game.Rulesets.Edit Debug.Assert(times.Length == 3); - return (times[0] * 60 + times[1]) * 1_000 + times[2]; - } - - public static List GetSelectedHitObjects(HitObjectComposer composer, IReadOnlyList editorHitObjects, string objectsGroup, double position) - { - List hitObjects = editorHitObjects.Where(x => x.StartTime >= position).ToList(); - List selectedObjects = new List(); - - string[] objectsToSelect = objectsGroup.Split(composer.ObjectSeparator).ToArray(); - - foreach (string objectInfo in objectsToSelect) - { - HitObject? current = hitObjects.FirstOrDefault(x => composer.HandleHitObjectSelection(x, objectInfo)); - - if (current == null) - continue; - - selectedObjects.Add(current); - hitObjects = hitObjects.Where(x => x != current && x.StartTime >= current.StartTime).ToList(); - } - - // Stable behavior - // - always selects the next closest object when `objectsGroup` only has one (combo) item - if (objectsToSelect.Length != 1 || objectsGroup.Contains('|')) - return selectedObjects; - - HitObject? nextClosest = editorHitObjects.FirstOrDefault(x => x.StartTime >= position); - if (nextClosest == null) - return selectedObjects; - - if (nextClosest.StartTime <= (selectedObjects.FirstOrDefault()?.StartTime ?? position)) - { - selectedObjects.Clear(); - selectedObjects.Add(nextClosest); - } - - return selectedObjects; + return (times[0] * 60 + times[1]) * 1000 + times[2]; } } } diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index f6cddcc0d2..52a525e84f 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -529,17 +529,11 @@ namespace osu.Game.Rulesets.Edit public virtual string ConvertSelectionToString() => string.Empty; /// - /// The custom logic that decides whether a HitObject should be selected when clicking an editor timestamp link + /// Each ruleset has it's own selection method /// - /// The hitObject being checked - /// A single hitObject's information created with - /// Whether a HitObject should be selected or not - public virtual bool HandleHitObjectSelection(HitObject hitObject, string objectInfo) => false; - - /// - /// A character that separates the selection in - /// - public virtual char ObjectSeparator => ','; + /// The given timestamp + /// The selected object information between the brackets + public virtual void SelectHitObjects(double timestamp, string objectDescription) { } #region IPositionSnapProvider diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 58c3ae809c..03d3e3a1f8 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -1194,15 +1194,8 @@ namespace osu.Game.Screens.Edit if (Mode.Value != EditorScreenMode.Compose) Mode.Value = EditorScreenMode.Compose; - List selected = EditorTimestampParser.GetSelectedHitObjects( - currentScreen.Dependencies.Get(), - editorBeatmap.HitObjects.ToList(), - objectsGroup, - position - ); - - if (selected.Any()) - editorBeatmap.SelectedHitObjects.AddRange(selected); + // Let the Ruleset handle selection + currentScreen.Dependencies.Get().SelectHitObjects(position, objectsGroup); } public double SnapTime(double time, double? referenceTime) => editorBeatmap.SnapTime(time, referenceTime); From 6ddecfd8062d6b1b0d62a6064d7d0b0b2c8d4760 Mon Sep 17 00:00:00 2001 From: ratinfx Date: Sat, 11 Nov 2023 14:13:06 +0100 Subject: [PATCH 18/91] Update Test cases --- .../TestSceneOpenEditorTimestampInMania.cs | 9 ++---- .../TestSceneOpenEditorTimestampInOsu.cs | 32 +++++-------------- 2 files changed, 11 insertions(+), 30 deletions(-) diff --git a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneOpenEditorTimestampInMania.cs b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneOpenEditorTimestampInMania.cs index 6ec5dcee4c..3c6a9f3b42 100644 --- a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneOpenEditorTimestampInMania.cs +++ b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneOpenEditorTimestampInMania.cs @@ -84,17 +84,14 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor [Test] public void TestUnusualSelection() { - addStepClickLink("00:00:000 (0|1)", "invalid link"); + addStepClickLink("00:00:000 (0|1)", "wrong offset"); AddAssert("snap to 1, select none", () => checkSnapAndSelectColumn(2_170)); addReset(); - addStepClickLink("00:00:000 (0)", "std link"); - AddAssert("snap and select 1", () => checkSnapAndSelectColumn(2_170, new List<(int, int)> - { (2_170, 2) }) - ); + addStepClickLink("00:00:000 (2)", "std link"); + AddAssert("snap to 1, select none", () => checkSnapAndSelectColumn(2_170)); addReset(); - // TODO: discuss - this selects the first 2 objects on Stable, do we want that or is this fine? addStepClickLink("00:00:000 (1,2)", "std link"); AddAssert("snap to 1, select none", () => checkSnapAndSelectColumn(2_170)); } diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOpenEditorTimestampInOsu.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOpenEditorTimestampInOsu.cs index d69f482d29..93573b5ad8 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOpenEditorTimestampInOsu.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOpenEditorTimestampInOsu.cs @@ -71,40 +71,24 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor { HitObject firstObject = null!; - addStepClickLink("00:00:000 (1,2,3)", "invalid offset"); - AddAssert("snap to next, select 1-2-3", () => + addStepClickLink("00:00:000 (0)", "invalid combo"); + AddAssert("snap to next, select none", () => { firstObject = EditorBeatmap.HitObjects.First(); - return checkSnapAndSelectCombo(firstObject.StartTime, 1, 2, 3); + return checkSnapAndSelectCombo(firstObject.StartTime); }); addReset(); - addStepClickLink("00:00:956 (2,3,4)", "invalid offset"); + addStepClickLink("00:00:000 (1)", "wrong offset"); + AddAssert("snap and select 1", () => checkSnapAndSelectCombo(firstObject.StartTime, 1)); + + addReset(); + addStepClickLink("00:00:956 (2,3,4)", "wrong offset"); AddAssert("snap to next, select 2-3-4", () => checkSnapAndSelectCombo(firstObject.StartTime, 2, 3, 4)); - addReset(); - addStepClickLink("00:00:000 (0)", "invalid offset"); - AddAssert("snap and select 1", () => checkSnapAndSelectCombo(firstObject.StartTime, 1)); - - addReset(); - addStepClickLink("00:00:000 (1)", "invalid offset"); - AddAssert("snap and select 1", () => checkSnapAndSelectCombo(firstObject.StartTime, 1)); - - addReset(); - addStepClickLink("00:00:000 (2)", "invalid offset"); - AddAssert("snap and select 1", () => checkSnapAndSelectCombo(firstObject.StartTime, 1)); - - addReset(); - addStepClickLink("00:00:000 (2,3)", "invalid offset"); - AddAssert("snap to 1, select 2-3", () => checkSnapAndSelectCombo(firstObject.StartTime, 2, 3)); - addReset(); addStepClickLink("00:00:956 (956|1,956|2)", "mania link"); AddAssert("snap to next, select none", () => checkSnapAndSelectCombo(firstObject.StartTime)); - - addReset(); - addStepClickLink("00:00:000 (0|1)", "mania link"); - AddAssert("snap to 1, select none", () => checkSnapAndSelectCombo(firstObject.StartTime)); } } } From 54b8244a18ad3e74d40962be20cab996ba63e90d Mon Sep 17 00:00:00 2001 From: cs Date: Sat, 11 Nov 2023 15:02:06 +0100 Subject: [PATCH 19/91] CI Fixup --- .../Sliders/Components/PathControlPointPiece.cs | 8 ++++---- .../Components/PathControlPointVisualiser.cs | 2 +- .../Sliders/SliderPlacementBlueprint.cs | 2 ++ .../Edit/OsuSliderDrawingSettingsProvider.cs | 10 ++++------ .../Visual/Gameplay/TestSceneBezierConverter.cs | 4 ++-- .../Visual/Gameplay/TestSceneSliderPath.cs | 4 ++-- .../Beatmaps/Formats/LegacyBeatmapEncoder.cs | 11 +++++------ osu.Game/Rulesets/Objects/BezierConverter.cs | 12 ++++++------ .../Objects/Legacy/ConvertHitObjectParser.cs | 3 ++- osu.Game/Rulesets/Objects/SliderPath.cs | 3 +-- osu.Game/Rulesets/Objects/Types/PathType.cs | 16 ++++++++-------- 11 files changed, 37 insertions(+), 38 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs index 9658e5f6c3..53228cff82 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs @@ -261,17 +261,17 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components switch (pathType) { - case { SplineType: SplineType.Catmull }: + case { Type: SplineType.Catmull }: return colours.SeaFoam; - case { SplineType: SplineType.BSpline, Degree: null }: + case { Type: SplineType.BSpline, Degree: null }: return colours.PinkLighter; - case { SplineType: SplineType.BSpline, Degree: >= 1 }: + case { Type: SplineType.BSpline, Degree: >= 1 }: int idx = Math.Clamp(pathType.Degree.Value, 0, 3); return new[] { colours.PinkDarker, colours.PinkDark, colours.Pink, colours.PinkLight }[idx]; - case { SplineType: SplineType.PerfectCurve }: + case { Type: SplineType.PerfectCurve }: return colours.PurpleDark; default: diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs index b5c9016538..4e85835652 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs @@ -242,7 +242,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components { int indexInSegment = piece.PointsInSegment.IndexOf(piece.ControlPoint); - if (type.HasValue && type.Value.SplineType == SplineType.PerfectCurve) + if (type.HasValue && type.Value.Type == SplineType.PerfectCurve) { // Can't always create a circular arc out of 4 or more points, // so we split the segment into one 3-point circular arc segment diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs index a5c6ae9465..20f11c3585 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs @@ -173,9 +173,11 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders setState(SliderPlacementState.Drawing); return true; } + return false; } } + return base.OnDragStart(e); } diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSliderDrawingSettingsProvider.cs b/osu.Game.Rulesets.Osu/Edit/OsuSliderDrawingSettingsProvider.cs index ba2c39e1b5..ae772f53fc 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuSliderDrawingSettingsProvider.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuSliderDrawingSettingsProvider.cs @@ -19,7 +19,7 @@ namespace osu.Game.Rulesets.Osu.Edit Precision = 0.01f }; - private BindableInt sliderTolerance = new BindableInt(10) + private readonly BindableInt sliderTolerance = new BindableInt(10) { MinValue = 5, MaxValue = 100 @@ -27,8 +27,6 @@ namespace osu.Game.Rulesets.Osu.Edit private ExpandableSlider toleranceSlider = null!; - private EditorToolboxGroup? toolboxGroup; - public OsuSliderDrawingSettingsProvider() { sliderTolerance.BindValueChanged(v => @@ -47,7 +45,7 @@ namespace osu.Game.Rulesets.Osu.Edit public void AttachToToolbox(ExpandingToolboxContainer toolboxContainer) { - toolboxContainer.Add(toolboxGroup = new EditorToolboxGroup("drawing") + toolboxContainer.Add(new EditorToolboxGroup("drawing") { Children = new Drawable[] { @@ -60,8 +58,8 @@ namespace osu.Game.Rulesets.Osu.Edit sliderTolerance.BindValueChanged(e => { - toleranceSlider.ContractedLabelText = $"Tolerance: {e.NewValue:N0}"; - toleranceSlider.ExpandedLabelText = $"Tolerance: {e.NewValue:N0}"; + toleranceSlider.ContractedLabelText = $"C. P. S.: {e.NewValue:N0}"; + toleranceSlider.ExpandedLabelText = $"Control Point Spacing: {e.NewValue:N0}"; }, true); } } diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneBezierConverter.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneBezierConverter.cs index 5eb82ccbdc..e2333011c7 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneBezierConverter.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneBezierConverter.cs @@ -120,7 +120,7 @@ namespace osu.Game.Tests.Visual.Gameplay [TestCase(SplineType.Catmull, null)] [TestCase(SplineType.PerfectCurve, null)] public void TestSingleSegment(SplineType splineType, int? degree) - => AddStep("create path", () => path.ControlPoints.AddRange(createSegment(new PathType { SplineType = splineType, Degree = degree }, Vector2.Zero, new Vector2(0, 100), new Vector2(100)))); + => AddStep("create path", () => path.ControlPoints.AddRange(createSegment(new PathType { Type = splineType, Degree = degree }, Vector2.Zero, new Vector2(0, 100), new Vector2(100)))); [TestCase(SplineType.Linear, null)] [TestCase(SplineType.BSpline, null)] @@ -132,7 +132,7 @@ namespace osu.Game.Tests.Visual.Gameplay AddStep("create path", () => { path.ControlPoints.AddRange(createSegment(PathType.LINEAR, Vector2.Zero)); - path.ControlPoints.AddRange(createSegment(new PathType { SplineType = splineType, Degree = degree }, new Vector2(0, 100), new Vector2(100), Vector2.Zero)); + path.ControlPoints.AddRange(createSegment(new PathType { Type = splineType, Degree = degree }, new Vector2(0, 100), new Vector2(100), Vector2.Zero)); }); } diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSliderPath.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSliderPath.cs index e4d99f6741..d44af45fe4 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSliderPath.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSliderPath.cs @@ -59,7 +59,7 @@ namespace osu.Game.Tests.Visual.Gameplay [TestCase(SplineType.PerfectCurve, null)] public void TestSingleSegment(SplineType splineType, int? degree) => AddStep("create path", () => path.ControlPoints.AddRange(createSegment( - new PathType { SplineType = splineType, Degree = degree }, + new PathType { Type = splineType, Degree = degree }, Vector2.Zero, new Vector2(0, 100), new Vector2(100), @@ -77,7 +77,7 @@ namespace osu.Game.Tests.Visual.Gameplay AddStep("create path", () => { path.ControlPoints.AddRange(createSegment(PathType.LINEAR, Vector2.Zero)); - path.ControlPoints.AddRange(createSegment(new PathType { SplineType = splineType, Degree = degree }, new Vector2(0, 100), new Vector2(100), Vector2.Zero)); + path.ControlPoints.AddRange(createSegment(new PathType { Type = splineType, Degree = degree }, new Vector2(0, 100), new Vector2(100), Vector2.Zero)); }); } diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs index 7029f61459..ff446206ac 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; -using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; @@ -456,23 +455,23 @@ namespace osu.Game.Beatmaps.Formats { switch (point.Type) { - case { SplineType: SplineType.BSpline, Degree: > 0 }: + case { Type: SplineType.BSpline, Degree: > 0 }: writer.Write($"B{point.Type.Value.Degree}|"); break; - case { SplineType: SplineType.BSpline, Degree: <= 0 }: + case { Type: SplineType.BSpline, Degree: <= 0 }: writer.Write("B|"); break; - case { SplineType: SplineType.Catmull }: + case { Type: SplineType.Catmull }: writer.Write("C|"); break; - case { SplineType: SplineType.PerfectCurve }: + case { Type: SplineType.PerfectCurve }: writer.Write("P|"); break; - case { SplineType: SplineType.Linear }: + case { Type: SplineType.Linear }: writer.Write("L|"); break; } diff --git a/osu.Game/Rulesets/Objects/BezierConverter.cs b/osu.Game/Rulesets/Objects/BezierConverter.cs index 74fbe7d8f9..ed86fc10e0 100644 --- a/osu.Game/Rulesets/Objects/BezierConverter.cs +++ b/osu.Game/Rulesets/Objects/BezierConverter.cs @@ -72,15 +72,15 @@ namespace osu.Game.Rulesets.Objects switch (segmentType) { - case { SplineType: SplineType.Catmull }: + case { Type: SplineType.Catmull }: result.AddRange(from segment in ConvertCatmullToBezierAnchors(segmentVertices) from v in segment select v + position); break; - case { SplineType: SplineType.Linear }: + case { Type: SplineType.Linear }: result.AddRange(from segment in ConvertLinearToBezierAnchors(segmentVertices) from v in segment select v + position); break; - case { SplineType: SplineType.PerfectCurve }: + case { Type: SplineType.PerfectCurve }: result.AddRange(ConvertCircleToBezierAnchors(segmentVertices).Select(v => v + position)); break; @@ -128,7 +128,7 @@ namespace osu.Game.Rulesets.Objects switch (segmentType) { - case { SplineType: SplineType.Catmull }: + case { Type: SplineType.Catmull }: foreach (var segment in ConvertCatmullToBezierAnchors(segmentVertices)) { for (int j = 0; j < segment.Length - 1; j++) @@ -139,7 +139,7 @@ namespace osu.Game.Rulesets.Objects break; - case { SplineType: SplineType.Linear }: + case { Type: SplineType.Linear }: foreach (var segment in ConvertLinearToBezierAnchors(segmentVertices)) { for (int j = 0; j < segment.Length - 1; j++) @@ -150,7 +150,7 @@ namespace osu.Game.Rulesets.Objects break; - case { SplineType: SplineType.PerfectCurve }: + case { Type: SplineType.PerfectCurve }: var circleResult = ConvertCircleToBezierAnchors(segmentVertices); for (int j = 0; j < circleResult.Length - 1; j++) diff --git a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs index 30f4c092d9..6a13a897c4 100644 --- a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs @@ -228,7 +228,8 @@ namespace osu.Game.Rulesets.Objects.Legacy case 'B': if (input.Length > 1 && int.TryParse(input.Substring(1), out int degree) && degree > 0) - return new PathType { SplineType = SplineType.BSpline, Degree = degree }; + return new PathType { Type = SplineType.BSpline, Degree = degree }; + return new PathType(SplineType.BSpline); case 'L': diff --git a/osu.Game/Rulesets/Objects/SliderPath.cs b/osu.Game/Rulesets/Objects/SliderPath.cs index 75f1ab868d..e9a192669f 100644 --- a/osu.Game/Rulesets/Objects/SliderPath.cs +++ b/osu.Game/Rulesets/Objects/SliderPath.cs @@ -6,7 +6,6 @@ using System.Collections.Generic; using System.Collections.Specialized; using System.Diagnostics; using System.Linq; -using Microsoft.Toolkit.HighPerformance; using Newtonsoft.Json; using osu.Framework.Bindables; using osu.Framework.Caching; @@ -289,7 +288,7 @@ namespace osu.Game.Rulesets.Objects private List calculateSubPath(ReadOnlySpan subControlPoints, PathType type) { - switch (type.SplineType) + switch (type.Type) { case SplineType.Linear: return PathApproximator.LinearToPiecewiseLinear(subControlPoints); diff --git a/osu.Game/Rulesets/Objects/Types/PathType.cs b/osu.Game/Rulesets/Objects/Types/PathType.cs index 41472fd8b5..a6e8e173d4 100644 --- a/osu.Game/Rulesets/Objects/Types/PathType.cs +++ b/osu.Game/Rulesets/Objects/Types/PathType.cs @@ -14,7 +14,7 @@ namespace osu.Game.Rulesets.Objects.Types PerfectCurve } - public struct PathType + public readonly struct PathType { public static readonly PathType CATMULL = new PathType(SplineType.Catmull); public static readonly PathType BEZIER = new PathType(SplineType.BSpline); @@ -24,36 +24,36 @@ namespace osu.Game.Rulesets.Objects.Types /// /// The type of the spline that should be used to interpret the control points of the path. /// - public SplineType SplineType { get; init; } + public SplineType Type { get; init; } /// - /// The degree of a BSpline. Unused if is not . + /// The degree of a BSpline. Unused if is not . /// Null means the degree is equal to the number of control points, 1 means linear, 2 means quadratic, etc. /// public int? Degree { get; init; } public PathType(SplineType splineType) { - SplineType = splineType; + Type = splineType; Degree = null; } public override int GetHashCode() - => HashCode.Combine(SplineType, Degree); + => HashCode.Combine(Type, Degree); public override bool Equals(object? obj) => obj is PathType pathType && this == pathType; public static bool operator ==(PathType a, PathType b) - => a.SplineType == b.SplineType && a.Degree == b.Degree; + => a.Type == b.Type && a.Degree == b.Degree; public static bool operator !=(PathType a, PathType b) - => a.SplineType != b.SplineType || a.Degree != b.Degree; + => a.Type != b.Type || a.Degree != b.Degree; public static PathType BSpline(int degree) { Debug.Assert(degree > 0); - return new PathType { SplineType = SplineType.BSpline, Degree = degree }; + return new PathType { Type = SplineType.BSpline, Degree = degree }; } } } From c367697559dc8981ce601e44afa45a3ce46a6dc9 Mon Sep 17 00:00:00 2001 From: ratinfx Date: Sat, 11 Nov 2023 16:14:26 +0100 Subject: [PATCH 20/91] Changed TIME_REGEX to accept everything in the parentheses - changed osu-web link to the current value --- osu.Game/Rulesets/Edit/EditorTimestampParser.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Rulesets/Edit/EditorTimestampParser.cs b/osu.Game/Rulesets/Edit/EditorTimestampParser.cs index 7d4247b269..b1488d298f 100644 --- a/osu.Game/Rulesets/Edit/EditorTimestampParser.cs +++ b/osu.Game/Rulesets/Edit/EditorTimestampParser.cs @@ -11,8 +11,8 @@ namespace osu.Game.Rulesets.Edit public static class EditorTimestampParser { // 00:00:000 (1,2,3) - test - // regex from https://github.com/ppy/osu-web/blob/651a9bac2b60d031edd7e33b8073a469bf11edaa/resources/assets/coffee/_classes/beatmap-discussion-helper.coffee#L10 - public static readonly Regex TIME_REGEX = new Regex(@"\b(((\d{2,}):([0-5]\d)[:.](\d{3}))(\s\((?:\d+[,|])*\d+\))?)"); + // osu-web regex: https://github.com/ppy/osu-web/blob/3b1698639244cfdaf0b41c68bfd651ea729ec2e3/resources/js/utils/beatmapset-discussion-helper.ts#L78 + public static readonly Regex TIME_REGEX = new Regex(@"\b(((\d{2,}):([0-5]\d)[:.](\d{3}))(\s\([^)]+\))?)"); public static string[] GetRegexGroups(string timestamp) { From 4e7c40f1d7392376d68989ad5fa26e53430c55c6 Mon Sep 17 00:00:00 2001 From: ratinfx Date: Sun, 12 Nov 2023 14:58:46 +0100 Subject: [PATCH 21/91] Do Split and Parse before checking HitObjects --- .../Edit/ManiaHitObjectComposer.cs | 28 ++++++++----------- .../Edit/OsuHitObjectComposer.cs | 19 +++++-------- 2 files changed, 18 insertions(+), 29 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs b/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs index b04d3f895d..d580f2d025 100644 --- a/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs +++ b/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs @@ -60,33 +60,27 @@ namespace osu.Game.Rulesets.Mania.Edit return; List remainingHitObjects = EditorBeatmap.HitObjects.Cast().Where(h => h.StartTime >= timestamp).ToList(); - string[] split = objectDescription.Split(',').ToArray(); + string[] splitDescription = objectDescription.Split(',').ToArray(); - for (int i = 0; i < split.Length; i++) + for (int i = 0; i < splitDescription.Length; i++) { - ManiaHitObject current = remainingHitObjects.FirstOrDefault(h => shouldBeSelected(h, split[i])); + string[] split = splitDescription[i].Split('|').ToArray(); + if (split.Length != 2) + continue; + + if (!double.TryParse(split[0], out double time) || !int.TryParse(split[1], out int column)) + continue; + + ManiaHitObject current = remainingHitObjects.FirstOrDefault(h => h.StartTime == time && h.Column == column); if (current == null) continue; EditorBeatmap.SelectedHitObjects.Add(current); - if (i < split.Length - 1) + if (i < splitDescription.Length - 1) remainingHitObjects = remainingHitObjects.Where(h => h != current && h.StartTime >= current.StartTime).ToList(); } } - - private bool shouldBeSelected(ManiaHitObject hitObject, string objectInfo) - { - string[] split = objectInfo.Split('|').ToArray(); - if (split.Length != 2) - return false; - - if (!double.TryParse(split[0], out double time) || !int.TryParse(split[1], out int column)) - return false; - - return hitObject.StartTime == time - && hitObject.Column == column; - } } } diff --git a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs index e9c222b0e7..1dfa02fe83 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs @@ -115,30 +115,25 @@ namespace osu.Game.Rulesets.Osu.Edit return; List remainingHitObjects = EditorBeatmap.HitObjects.Cast().Where(h => h.StartTime >= timestamp).ToList(); - string[] split = objectDescription.Split(',').ToArray(); + string[] splitDescription = objectDescription.Split(',').ToArray(); - for (int i = 0; i < split.Length; i++) + for (int i = 0; i < splitDescription.Length; i++) { - OsuHitObject current = remainingHitObjects.FirstOrDefault(h => shouldBeSelected(h, split[i])); + if (!int.TryParse(splitDescription[i], out int combo) || combo < 1) + continue; + + OsuHitObject current = remainingHitObjects.FirstOrDefault(h => h.IndexInCurrentCombo + 1 == combo); if (current == null) continue; EditorBeatmap.SelectedHitObjects.Add(current); - if (i < split.Length - 1) + if (i < splitDescription.Length - 1) remainingHitObjects = remainingHitObjects.Where(h => h != current && h.StartTime >= current.StartTime).ToList(); } } - private bool shouldBeSelected(OsuHitObject hitObject, string objectInfo) - { - if (!int.TryParse(objectInfo, out int combo) || combo < 1) - return false; - - return hitObject.IndexInCurrentCombo + 1 == combo; - } - private DistanceSnapGrid distanceSnapGrid; private Container distanceSnapGridContainer; From fab6fc9adb633d82d88afe4c76b1313946032ab4 Mon Sep 17 00:00:00 2001 From: ratinfx Date: Sun, 12 Nov 2023 15:09:15 +0100 Subject: [PATCH 22/91] Updated comments, renamed method --- osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs | 1 + osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs | 1 + osu.Game/OsuGame.cs | 4 ++-- osu.Game/Rulesets/Edit/EditorTimestampParser.cs | 6 ++++-- 4 files changed, 8 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs b/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs index d580f2d025..92ecea812c 100644 --- a/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs +++ b/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs @@ -49,6 +49,7 @@ namespace osu.Game.Rulesets.Mania.Edit new HoldNoteCompositionTool() }; + // 123|0,456|1,789|2 ... private static readonly Regex selection_regex = new Regex(@"^\d+\|\d+(,\d+\|\d+)*$"); public override string ConvertSelectionToString() diff --git a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs index 1dfa02fe83..2afbd83ce5 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs @@ -104,6 +104,7 @@ namespace osu.Game.Rulesets.Osu.Edit protected override ComposeBlueprintContainer CreateBlueprintContainer() => new OsuBlueprintContainer(this); + // 1,2,3,4 ... private static readonly Regex selection_regex = new Regex(@"^\d+(,\d+)*$"); public override string ConvertSelectionToString() diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index cde8ee1457..4e37481ba7 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -434,7 +434,7 @@ namespace osu.Game break; case LinkAction.OpenEditorTimestamp: - SeekToTimestamp(argString); + HandleTimestamp(argString); break; case LinkAction.JoinMultiplayerMatch: @@ -558,7 +558,7 @@ namespace osu.Game /// Seek to a given timestamp in the Editor and select relevant HitObjects if needed /// /// The timestamp and the selected objects - public void SeekToTimestamp(string timestamp) + public void HandleTimestamp(string timestamp) { if (ScreenStack.CurrentScreen is not Editor editor) { diff --git a/osu.Game/Rulesets/Edit/EditorTimestampParser.cs b/osu.Game/Rulesets/Edit/EditorTimestampParser.cs index b1488d298f..e36822cc63 100644 --- a/osu.Game/Rulesets/Edit/EditorTimestampParser.cs +++ b/osu.Game/Rulesets/Edit/EditorTimestampParser.cs @@ -10,16 +10,18 @@ namespace osu.Game.Rulesets.Edit { public static class EditorTimestampParser { - // 00:00:000 (1,2,3) - test - // osu-web regex: https://github.com/ppy/osu-web/blob/3b1698639244cfdaf0b41c68bfd651ea729ec2e3/resources/js/utils/beatmapset-discussion-helper.ts#L78 + // 00:00:000 (...) - test + // original osu-web regex: https://github.com/ppy/osu-web/blob/3b1698639244cfdaf0b41c68bfd651ea729ec2e3/resources/js/utils/beatmapset-discussion-helper.ts#L78 public static readonly Regex TIME_REGEX = new Regex(@"\b(((\d{2,}):([0-5]\d)[:.](\d{3}))(\s\([^)]+\))?)"); public static string[] GetRegexGroups(string timestamp) { Match match = TIME_REGEX.Match(timestamp); + string[] result = match.Success ? match.Groups.Values.Where(x => x is not Match && !x.Value.Contains(':')).Select(x => x.Value).ToArray() : Array.Empty(); + return result; } From c24b3543ab271536b2b98c3c4777a174bc0621c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Mu=CC=88ller-Ho=CC=88hne?= Date: Mon, 13 Nov 2023 12:47:12 +0900 Subject: [PATCH 23/91] Fix OnDragStart on macOS --- .../Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs index 20f11c3585..fe01b1b6d2 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs @@ -165,7 +165,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders if (HitObject.Path.ControlPoints.Count < 3) { var lastCp = HitObject.Path.ControlPoints.LastOrDefault(); - if (lastCp != cursor) + if (lastCp != cursor && HitObject.Path.ControlPoints.Count == 2) return false; bSplineBuilder.Clear(); From e6b4dfba369ee790ee0cee427aa702a935ce341b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Mu=CC=88ller-Ho=CC=88hne?= Date: Mon, 13 Nov 2023 12:49:59 +0900 Subject: [PATCH 24/91] Fix doubled control point at beginning of drawn slider --- .../Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs index fe01b1b6d2..9ac28fe82a 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs @@ -195,7 +195,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders { var cps = self.bSplineBuilder.GetControlPoints(); self.HitObject.Path.ControlPoints.RemoveRange(1, self.HitObject.Path.ControlPoints.Count - 1); - self.HitObject.Path.ControlPoints.AddRange(cps.Select(v => new PathControlPoint(v))); + self.HitObject.Path.ControlPoints.AddRange(cps.Skip(1).Select(v => new PathControlPoint(v))); }, this); } From 8ad8764947835e9ce4070338b6e1ea81016df2b1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 13 Nov 2023 14:06:18 +0900 Subject: [PATCH 25/91] Update framework --- osu.Android.props | 2 +- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index 2870696c03..1f6a65c450 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -10,7 +10,7 @@ true - + diff --git a/osu.iOS.props b/osu.iOS.props index f1159f58b9..70525a5c59 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -23,6 +23,6 @@ iossimulator-x64 - + From fa976a5aa0eeb94dccbcd58b00d51a1c9d7e52df Mon Sep 17 00:00:00 2001 From: cs Date: Mon, 13 Nov 2023 08:24:09 +0100 Subject: [PATCH 26/91] Fix code style/quality issues --- .../TestSceneJuiceStreamSelectionBlueprint.cs | 6 +++--- .../JuiceStreamPathTest.cs | 2 +- .../Checks/CheckOffscreenObjectsTest.cs | 2 +- .../TestSceneOsuEditorSelectInvalidPath.cs | 2 +- .../TestScenePathControlPointVisualiser.cs | 8 ++++---- .../TestSceneSliderControlPointPiece.cs | 18 ++++++++--------- .../Editor/TestSceneSliderLengthValidity.cs | 2 +- .../TestSceneSliderPlacementBlueprint.cs | 16 +++++++-------- .../Editor/TestSceneSliderReversal.cs | 2 +- .../Editor/TestSceneSliderSnapping.cs | 10 +++++----- .../Editor/TestSceneSliderSplitting.cs | 20 +++++++++---------- .../TestSceneSlider.cs | 6 +++--- .../TestSceneSliderInput.cs | 2 +- .../TestSceneSliderSnaking.cs | 6 +++--- .../Components/PathControlPointPiece.cs | 14 ++++++------- .../Components/PathControlPointVisualiser.cs | 2 +- .../Sliders/SliderPlacementBlueprint.cs | 2 +- .../Edit/OsuSliderDrawingSettingsProvider.cs | 8 +++++--- .../Formats/LegacyBeatmapDecoderTest.cs | 8 ++++---- .../Gameplay/TestSceneBezierConverter.cs | 8 ++++---- .../Visual/Gameplay/TestSceneSliderPath.cs | 4 ++-- .../Beatmaps/Formats/LegacyBeatmapEncoder.cs | 18 +++++++---------- .../Edit/ComposerDistanceSnapProvider.cs | 2 +- osu.Game/Rulesets/Edit/IToolboxAttachment.cs | 10 ++++++++++ osu.Game/Rulesets/Objects/BezierConverter.cs | 8 ++++---- .../Objects/Legacy/ConvertHitObjectParser.cs | 10 +++++----- .../Rulesets/Objects/SliderPathExtensions.cs | 2 +- osu.Game/Rulesets/Objects/Types/PathType.cs | 12 +++++++---- osu.Game/osu.Game.csproj | 2 +- 29 files changed, 112 insertions(+), 100 deletions(-) create mode 100644 osu.Game/Rulesets/Edit/IToolboxAttachment.cs diff --git a/osu.Game.Rulesets.Catch.Tests/Editor/TestSceneJuiceStreamSelectionBlueprint.cs b/osu.Game.Rulesets.Catch.Tests/Editor/TestSceneJuiceStreamSelectionBlueprint.cs index 16b51d414a..c96f32d87c 100644 --- a/osu.Game.Rulesets.Catch.Tests/Editor/TestSceneJuiceStreamSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Catch.Tests/Editor/TestSceneJuiceStreamSelectionBlueprint.cs @@ -140,7 +140,7 @@ namespace osu.Game.Rulesets.Catch.Tests.Editor AddStep("update hit object path", () => { - hitObject.Path = new SliderPath(PathType.PERFECTCURVE, new[] + hitObject.Path = new SliderPath(PathType.PERFECT_CURVE, new[] { Vector2.Zero, new Vector2(100, 100), @@ -190,14 +190,14 @@ namespace osu.Game.Rulesets.Catch.Tests.Editor [Test] public void TestVertexResampling() { - addBlueprintStep(100, 100, new SliderPath(PathType.PERFECTCURVE, new[] + addBlueprintStep(100, 100, new SliderPath(PathType.PERFECT_CURVE, new[] { Vector2.Zero, new Vector2(100, 100), new Vector2(50, 200), }), 0.5); AddAssert("1 vertex per 1 nested HO", () => getVertices().Count == hitObject.NestedHitObjects.Count); - AddAssert("slider path not yet changed", () => hitObject.Path.ControlPoints[0].Type == PathType.PERFECTCURVE); + AddAssert("slider path not yet changed", () => hitObject.Path.ControlPoints[0].Type == PathType.PERFECT_CURVE); addAddVertexSteps(150, 150); AddAssert("slider path change to linear", () => hitObject.Path.ControlPoints[0].Type == PathType.LINEAR); } diff --git a/osu.Game.Rulesets.Catch.Tests/JuiceStreamPathTest.cs b/osu.Game.Rulesets.Catch.Tests/JuiceStreamPathTest.cs index 82f24633b5..9fb55fc057 100644 --- a/osu.Game.Rulesets.Catch.Tests/JuiceStreamPathTest.cs +++ b/osu.Game.Rulesets.Catch.Tests/JuiceStreamPathTest.cs @@ -154,7 +154,7 @@ namespace osu.Game.Rulesets.Catch.Tests } while (rng.Next(2) != 0); int length = sliderPath.ControlPoints.Count - start + 1; - sliderPath.ControlPoints[start].Type = length <= 2 ? PathType.LINEAR : length == 3 ? PathType.PERFECTCURVE : PathType.BEZIER; + sliderPath.ControlPoints[start].Type = length <= 2 ? PathType.LINEAR : length == 3 ? PathType.PERFECT_CURVE : PathType.BEZIER; } while (rng.Next(3) != 0); if (rng.Next(5) == 0) diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/Checks/CheckOffscreenObjectsTest.cs b/osu.Game.Rulesets.Osu.Tests/Editor/Checks/CheckOffscreenObjectsTest.cs index 8612a8eb57..5db6dc6cdd 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/Checks/CheckOffscreenObjectsTest.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/Checks/CheckOffscreenObjectsTest.cs @@ -214,7 +214,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor.Checks Path = new SliderPath(new[] { // Circular arc shoots over the top of the screen. - new PathControlPoint(new Vector2(0, 0), PathType.PERFECTCURVE), + new PathControlPoint(new Vector2(0, 0), PathType.PERFECT_CURVE), new PathControlPoint(new Vector2(-100, -200)), new PathControlPoint(new Vector2(100, -200)) }), diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditorSelectInvalidPath.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditorSelectInvalidPath.cs index 7ea4d40b90..43dae38004 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditorSelectInvalidPath.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditorSelectInvalidPath.cs @@ -25,7 +25,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor PathControlPoint[] points = { - new PathControlPoint(new Vector2(0), PathType.PERFECTCURVE), + new PathControlPoint(new Vector2(0), PathType.PERFECT_CURVE), new PathControlPoint(new Vector2(-100, 0)), new PathControlPoint(new Vector2(100, 20)) }; diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestScenePathControlPointVisualiser.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestScenePathControlPointVisualiser.cs index 16800997f4..811ecf53e9 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestScenePathControlPointVisualiser.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestScenePathControlPointVisualiser.cs @@ -64,7 +64,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor addContextMenuItemStep("Perfect curve"); assertControlPointPathType(0, PathType.BEZIER); - assertControlPointPathType(1, PathType.PERFECTCURVE); + assertControlPointPathType(1, PathType.PERFECT_CURVE); assertControlPointPathType(3, PathType.BEZIER); } @@ -84,7 +84,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor addContextMenuItemStep("Perfect curve"); assertControlPointPathType(0, PathType.BEZIER); - assertControlPointPathType(2, PathType.PERFECTCURVE); + assertControlPointPathType(2, PathType.PERFECT_CURVE); assertControlPointPathType(4, null); } @@ -124,7 +124,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor addContextMenuItemStep("Perfect curve"); assertControlPointPathType(0, PathType.LINEAR); - assertControlPointPathType(1, PathType.PERFECTCURVE); + assertControlPointPathType(1, PathType.PERFECT_CURVE); assertControlPointPathType(3, PathType.LINEAR); } @@ -134,7 +134,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor createVisualiser(true); addControlPointStep(new Vector2(200), PathType.BEZIER); - addControlPointStep(new Vector2(300), PathType.PERFECTCURVE); + addControlPointStep(new Vector2(300), PathType.PERFECT_CURVE); addControlPointStep(new Vector2(500, 300)); addControlPointStep(new Vector2(700, 200), PathType.BEZIER); addControlPointStep(new Vector2(500, 100)); diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderControlPointPiece.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderControlPointPiece.cs index 1d8d2cf01a..99ced30ffe 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderControlPointPiece.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderControlPointPiece.cs @@ -38,9 +38,9 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor Position = new Vector2(256, 192), Path = new SliderPath(new[] { - new PathControlPoint(Vector2.Zero, PathType.PERFECTCURVE), + new PathControlPoint(Vector2.Zero, PathType.PERFECT_CURVE), new PathControlPoint(new Vector2(150, 150)), - new PathControlPoint(new Vector2(300, 0), PathType.PERFECTCURVE), + new PathControlPoint(new Vector2(300, 0), PathType.PERFECT_CURVE), new PathControlPoint(new Vector2(400, 0)), new PathControlPoint(new Vector2(400, 150)) }) @@ -182,7 +182,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor AddStep("release", () => InputManager.ReleaseButton(MouseButton.Left)); assertControlPointPosition(1, new Vector2(150, 50)); - assertControlPointType(0, PathType.PERFECTCURVE); + assertControlPointType(0, PathType.PERFECT_CURVE); } [Test] @@ -210,7 +210,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor AddAssert("three control point pieces selected", () => this.ChildrenOfType>().Count(piece => piece.IsSelected.Value) == 3); assertControlPointPosition(2, new Vector2(450, 50)); - assertControlPointType(2, PathType.PERFECTCURVE); + assertControlPointType(2, PathType.PERFECT_CURVE); assertControlPointPosition(3, new Vector2(550, 50)); @@ -249,7 +249,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor AddAssert("slider moved", () => Precision.AlmostEquals(slider.Position, new Vector2(256, 192) + new Vector2(150, 50))); assertControlPointPosition(0, Vector2.Zero); - assertControlPointType(0, PathType.PERFECTCURVE); + assertControlPointType(0, PathType.PERFECT_CURVE); assertControlPointPosition(1, new Vector2(0, 100)); @@ -288,7 +288,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor AddStep("release", () => InputManager.ReleaseButton(MouseButton.Left)); assertControlPointPosition(1, new Vector2(150, 50)); - assertControlPointType(0, PathType.PERFECTCURVE); + assertControlPointType(0, PathType.PERFECT_CURVE); } [Test] @@ -304,7 +304,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor AddStep("release", () => InputManager.ReleaseButton(MouseButton.Left)); assertControlPointPosition(4, new Vector2(150, 150)); - assertControlPointType(2, PathType.PERFECTCURVE); + assertControlPointType(2, PathType.PERFECT_CURVE); } [Test] @@ -312,12 +312,12 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor { AddStep("change type to bezier", () => slider.Path.ControlPoints[2].Type = PathType.BEZIER); AddStep("add point", () => slider.Path.ControlPoints.Add(new PathControlPoint(new Vector2(500, 10)))); - AddStep("change type to perfect", () => slider.Path.ControlPoints[3].Type = PathType.PERFECTCURVE); + AddStep("change type to perfect", () => slider.Path.ControlPoints[3].Type = PathType.PERFECT_CURVE); moveMouseToControlPoint(4); AddStep("hold", () => InputManager.PressButton(MouseButton.Left)); - assertControlPointType(3, PathType.PERFECTCURVE); + assertControlPointType(3, PathType.PERFECT_CURVE); addMovementStep(new Vector2(350, 0.01f)); AddStep("release", () => InputManager.ReleaseButton(MouseButton.Left)); diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderLengthValidity.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderLengthValidity.cs index 38ebeb7e8f..931c8c9e63 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderLengthValidity.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderLengthValidity.cs @@ -126,7 +126,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor PathControlPoint[] points = { - new PathControlPoint(new Vector2(0), PathType.PERFECTCURVE), + new PathControlPoint(new Vector2(0), PathType.PERFECT_CURVE), new PathControlPoint(new Vector2(100, 0)), new PathControlPoint(new Vector2(0, 10)) }; diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderPlacementBlueprint.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderPlacementBlueprint.cs index 4b120c1a3f..ecfc8105f1 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderPlacementBlueprint.cs @@ -90,7 +90,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor assertPlaced(true); assertControlPointCount(3); assertControlPointPosition(1, new Vector2(100, 0)); - assertControlPointType(0, PathType.PERFECTCURVE); + assertControlPointType(0, PathType.PERFECT_CURVE); } [Test] @@ -172,7 +172,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor assertPlaced(true); assertControlPointCount(3); - assertControlPointType(0, PathType.PERFECTCURVE); + assertControlPointType(0, PathType.PERFECT_CURVE); } [Test] @@ -241,7 +241,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor assertControlPointPosition(1, new Vector2(100, 0)); assertControlPointPosition(2, new Vector2(100)); assertControlPointType(0, PathType.LINEAR); - assertControlPointType(1, PathType.PERFECTCURVE); + assertControlPointType(1, PathType.PERFECT_CURVE); } [Test] @@ -269,8 +269,8 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor assertControlPointPosition(2, new Vector2(100)); assertControlPointPosition(3, new Vector2(200, 100)); assertControlPointPosition(4, new Vector2(200)); - assertControlPointType(0, PathType.PERFECTCURVE); - assertControlPointType(2, PathType.PERFECTCURVE); + assertControlPointType(0, PathType.PERFECT_CURVE); + assertControlPointType(2, PathType.PERFECT_CURVE); } [Test] @@ -326,7 +326,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor assertPlaced(true); assertControlPointCount(3); - assertControlPointType(0, PathType.PERFECTCURVE); + assertControlPointType(0, PathType.PERFECT_CURVE); } [Test] @@ -347,7 +347,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor assertPlaced(true); assertControlPointCount(3); - assertControlPointType(0, PathType.PERFECTCURVE); + assertControlPointType(0, PathType.PERFECT_CURVE); } [Test] @@ -385,7 +385,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor assertPlaced(true); assertControlPointCount(3); - assertControlPointType(0, PathType.PERFECTCURVE); + assertControlPointType(0, PathType.PERFECT_CURVE); } private void addMovementStep(Vector2 position) => AddStep($"move mouse to {position}", () => InputManager.MoveMouseTo(InputManager.ToScreenSpace(position))); diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderReversal.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderReversal.cs index 0ddfc40946..a44c16a2e0 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderReversal.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderReversal.cs @@ -22,7 +22,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor private readonly PathControlPoint[][] paths = { createPathSegment( - PathType.PERFECTCURVE, + PathType.PERFECT_CURVE, new Vector2(200, -50), new Vector2(250, 0) ), diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSnapping.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSnapping.cs index c984d9168e..541fefb3a6 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSnapping.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSnapping.cs @@ -56,7 +56,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor { ControlPoints = { - new PathControlPoint(Vector2.Zero, PathType.PERFECTCURVE), + new PathControlPoint(Vector2.Zero, PathType.PERFECT_CURVE), new PathControlPoint(new Vector2(136, 205)), new PathControlPoint(new Vector2(-4, 226)) } @@ -181,7 +181,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor { OsuSelectionHandler selectionHandler; - AddAssert("first control point perfect", () => slider.Path.ControlPoints[0].Type == PathType.PERFECTCURVE); + AddAssert("first control point perfect", () => slider.Path.ControlPoints[0].Type == PathType.PERFECT_CURVE); AddStep("select slider", () => EditorBeatmap.SelectedHitObjects.Add(slider)); AddStep("rotate 90 degrees ccw", () => @@ -190,7 +190,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor selectionHandler.HandleRotation(-90); }); - AddAssert("first control point still perfect", () => slider.Path.ControlPoints[0].Type == PathType.PERFECTCURVE); + AddAssert("first control point still perfect", () => slider.Path.ControlPoints[0].Type == PathType.PERFECT_CURVE); } [Test] @@ -223,7 +223,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor { OsuSelectionHandler selectionHandler; - AddAssert("first control point perfect", () => slider.Path.ControlPoints[0].Type == PathType.PERFECTCURVE); + AddAssert("first control point perfect", () => slider.Path.ControlPoints[0].Type == PathType.PERFECT_CURVE); AddStep("select slider", () => EditorBeatmap.SelectedHitObjects.Add(slider)); AddStep("flip slider horizontally", () => @@ -232,7 +232,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor selectionHandler.OnPressed(new KeyBindingPressEvent(InputManager.CurrentState, GlobalAction.EditorFlipVertically)); }); - AddAssert("first control point still perfect", () => slider.Path.ControlPoints[0].Type == PathType.PERFECTCURVE); + AddAssert("first control point still perfect", () => slider.Path.ControlPoints[0].Type == PathType.PERFECT_CURVE); } [Test] diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSplitting.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSplitting.cs index cded9165f4..6c7733e68a 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSplitting.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSplitting.cs @@ -45,9 +45,9 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor Position = new Vector2(0, 50), Path = new SliderPath(new[] { - new PathControlPoint(Vector2.Zero, PathType.PERFECTCURVE), + new PathControlPoint(Vector2.Zero, PathType.PERFECT_CURVE), new PathControlPoint(new Vector2(150, 150)), - new PathControlPoint(new Vector2(300, 0), PathType.PERFECTCURVE), + new PathControlPoint(new Vector2(300, 0), PathType.PERFECT_CURVE), new PathControlPoint(new Vector2(400, 0)), new PathControlPoint(new Vector2(400, 150)) }) @@ -73,20 +73,20 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor AddAssert("slider split", () => slider is not null && EditorBeatmap.HitObjects.Count == 2 && sliderCreatedFor((Slider)EditorBeatmap.HitObjects[0], 0, EditorBeatmap.HitObjects[1].StartTime - split_gap, - (new Vector2(0, 50), PathType.PERFECTCURVE), + (new Vector2(0, 50), PathType.PERFECT_CURVE), (new Vector2(150, 200), null), (new Vector2(300, 50), null) ) && sliderCreatedFor((Slider)EditorBeatmap.HitObjects[1], slider.StartTime, endTime + split_gap, - (new Vector2(300, 50), PathType.PERFECTCURVE), + (new Vector2(300, 50), PathType.PERFECT_CURVE), (new Vector2(400, 50), null), (new Vector2(400, 200), null) )); AddStep("undo", () => Editor.Undo()); AddAssert("original slider restored", () => EditorBeatmap.HitObjects.Count == 1 && sliderCreatedFor((Slider)EditorBeatmap.HitObjects[0], 0, endTime, - (new Vector2(0, 50), PathType.PERFECTCURVE), + (new Vector2(0, 50), PathType.PERFECT_CURVE), (new Vector2(150, 200), null), - (new Vector2(300, 50), PathType.PERFECTCURVE), + (new Vector2(300, 50), PathType.PERFECT_CURVE), (new Vector2(400, 50), null), (new Vector2(400, 200), null) )); @@ -104,7 +104,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor Position = new Vector2(0, 50), Path = new SliderPath(new[] { - new PathControlPoint(Vector2.Zero, PathType.PERFECTCURVE), + new PathControlPoint(Vector2.Zero, PathType.PERFECT_CURVE), new PathControlPoint(new Vector2(150, 150)), new PathControlPoint(new Vector2(300, 0), PathType.BEZIER), new PathControlPoint(new Vector2(400, 0)), @@ -139,7 +139,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor AddAssert("slider split", () => slider is not null && EditorBeatmap.HitObjects.Count == 3 && sliderCreatedFor((Slider)EditorBeatmap.HitObjects[0], 0, EditorBeatmap.HitObjects[1].StartTime - split_gap, - (new Vector2(0, 50), PathType.PERFECTCURVE), + (new Vector2(0, 50), PathType.PERFECT_CURVE), (new Vector2(150, 200), null), (new Vector2(300, 50), null) ) && sliderCreatedFor((Slider)EditorBeatmap.HitObjects[1], EditorBeatmap.HitObjects[0].GetEndTime() + split_gap, slider.StartTime - split_gap, @@ -165,9 +165,9 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor Position = new Vector2(0, 50), Path = new SliderPath(new[] { - new PathControlPoint(Vector2.Zero, PathType.PERFECTCURVE), + new PathControlPoint(Vector2.Zero, PathType.PERFECT_CURVE), new PathControlPoint(new Vector2(150, 150)), - new PathControlPoint(new Vector2(300, 0), PathType.PERFECTCURVE), + new PathControlPoint(new Vector2(300, 0), PathType.PERFECT_CURVE), new PathControlPoint(new Vector2(400, 0)), new PathControlPoint(new Vector2(400, 150)) }) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSlider.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSlider.cs index 60003e7950..4600db8174 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSlider.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSlider.cs @@ -219,7 +219,7 @@ namespace osu.Game.Rulesets.Osu.Tests { StartTime = Time.Current + time_offset, Position = new Vector2(239, 176), - Path = new SliderPath(PathType.PERFECTCURVE, new[] + Path = new SliderPath(PathType.PERFECT_CURVE, new[] { Vector2.Zero, new Vector2(154, 28), @@ -255,7 +255,7 @@ namespace osu.Game.Rulesets.Osu.Tests SliderVelocityMultiplier = speedMultiplier, StartTime = Time.Current + time_offset, Position = new Vector2(0, -(distance / 2)), - Path = new SliderPath(PathType.PERFECTCURVE, new[] + Path = new SliderPath(PathType.PERFECT_CURVE, new[] { Vector2.Zero, new Vector2(0, distance), @@ -273,7 +273,7 @@ namespace osu.Game.Rulesets.Osu.Tests { StartTime = Time.Current + time_offset, Position = new Vector2(-max_length / 2, 0), - Path = new SliderPath(PathType.PERFECTCURVE, new[] + Path = new SliderPath(PathType.PERFECT_CURVE, new[] { Vector2.Zero, new Vector2(max_length / 2, max_length / 2), diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderInput.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderInput.cs index 08836ef819..0f7dd8b7bc 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderInput.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderInput.cs @@ -478,7 +478,7 @@ namespace osu.Game.Rulesets.Osu.Tests StartTime = time_slider_start, Position = new Vector2(0, 0), SliderVelocityMultiplier = 0.1f, - Path = new SliderPath(PathType.PERFECTCURVE, new[] + Path = new SliderPath(PathType.PERFECT_CURVE, new[] { Vector2.Zero, new Vector2(slider_path_length, 0), diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs index ebc5143aed..912b2b0626 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs @@ -217,7 +217,7 @@ namespace osu.Game.Rulesets.Osu.Tests { StartTime = 3000, Position = new Vector2(100, 100), - Path = new SliderPath(PathType.PERFECTCURVE, new[] + Path = new SliderPath(PathType.PERFECT_CURVE, new[] { Vector2.Zero, new Vector2(300, 200) @@ -227,7 +227,7 @@ namespace osu.Game.Rulesets.Osu.Tests { StartTime = 13000, Position = new Vector2(100, 100), - Path = new SliderPath(PathType.PERFECTCURVE, new[] + Path = new SliderPath(PathType.PERFECT_CURVE, new[] { Vector2.Zero, new Vector2(300, 200) @@ -238,7 +238,7 @@ namespace osu.Game.Rulesets.Osu.Tests { StartTime = 23000, Position = new Vector2(100, 100), - Path = new SliderPath(PathType.PERFECTCURVE, new[] + Path = new SliderPath(PathType.PERFECT_CURVE, new[] { Vector2.Zero, new Vector2(300, 200) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs index 53228cff82..ac9048d5c7 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs @@ -221,7 +221,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components /// private void updatePathType() { - if (ControlPoint.Type != PathType.PERFECTCURVE) + if (ControlPoint.Type != PathType.PERFECT_CURVE) return; if (PointsInSegment.Count > 3) @@ -259,19 +259,19 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components if (ControlPoint.Type is not PathType pathType) return colours.Yellow; - switch (pathType) + switch (pathType.Type) { - case { Type: SplineType.Catmull }: + case SplineType.Catmull: return colours.SeaFoam; - case { Type: SplineType.BSpline, Degree: null }: - return colours.PinkLighter; + case SplineType.BSpline: + if (!pathType.Degree.HasValue) + return colours.PinkLighter; - case { Type: SplineType.BSpline, Degree: >= 1 }: int idx = Math.Clamp(pathType.Degree.Value, 0, 3); return new[] { colours.PinkDarker, colours.PinkDark, colours.Pink, colours.PinkLight }[idx]; - case { Type: SplineType.PerfectCurve }: + case SplineType.PerfectCurve: return colours.PurpleDark; default: diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs index 4e85835652..5ab050ed48 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs @@ -368,7 +368,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components // todo: hide/disable items which aren't valid for selected points curveTypeItems.Add(createMenuItemForPathType(PathType.LINEAR)); - curveTypeItems.Add(createMenuItemForPathType(PathType.PERFECTCURVE)); + curveTypeItems.Add(createMenuItemForPathType(PathType.PERFECT_CURVE)); curveTypeItems.Add(createMenuItemForPathType(PathType.BEZIER)); curveTypeItems.Add(createMenuItemForPathType(PathType.BSpline(3))); curveTypeItems.Add(createMenuItemForPathType(PathType.CATMULL)); diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs index 9ac28fe82a..c722f0fdbc 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs @@ -251,7 +251,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders break; case 3: - segmentStart.Type = PathType.PERFECTCURVE; + segmentStart.Type = PathType.PERFECT_CURVE; break; default: diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSliderDrawingSettingsProvider.cs b/osu.Game.Rulesets.Osu/Edit/OsuSliderDrawingSettingsProvider.cs index ae772f53fc..643732d90a 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuSliderDrawingSettingsProvider.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuSliderDrawingSettingsProvider.cs @@ -10,7 +10,7 @@ using osu.Game.Rulesets.Edit; namespace osu.Game.Rulesets.Osu.Edit { - public partial class OsuSliderDrawingSettingsProvider : Drawable, ISliderDrawingSettingsProvider + public partial class OsuSliderDrawingSettingsProvider : Drawable, ISliderDrawingSettingsProvider, IToolboxAttachment { public BindableFloat Tolerance { get; } = new BindableFloat(0.1f) { @@ -27,12 +27,14 @@ namespace osu.Game.Rulesets.Osu.Edit private ExpandableSlider toleranceSlider = null!; - public OsuSliderDrawingSettingsProvider() + protected override void LoadComplete() { + base.LoadComplete(); + sliderTolerance.BindValueChanged(v => { float newValue = v.NewValue / 100f; - if (!Precision.AlmostEquals(newValue, Tolerance.Value, 1e-7f)) + if (!Precision.AlmostEquals(newValue, Tolerance.Value)) Tolerance.Value = newValue; }); Tolerance.BindValueChanged(v => diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs index 18c21046fb..34b96bbd3f 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs @@ -808,7 +808,7 @@ namespace osu.Game.Tests.Beatmaps.Formats var first = ((IHasPath)decoded.HitObjects[0]).Path; Assert.That(first.ControlPoints[0].Position, Is.EqualTo(Vector2.Zero)); - Assert.That(first.ControlPoints[0].Type, Is.EqualTo(PathType.PERFECTCURVE)); + Assert.That(first.ControlPoints[0].Type, Is.EqualTo(PathType.PERFECT_CURVE)); Assert.That(first.ControlPoints[1].Position, Is.EqualTo(new Vector2(161, -244))); Assert.That(first.ControlPoints[1].Type, Is.EqualTo(null)); @@ -827,7 +827,7 @@ namespace osu.Game.Tests.Beatmaps.Formats var second = ((IHasPath)decoded.HitObjects[1]).Path; Assert.That(second.ControlPoints[0].Position, Is.EqualTo(Vector2.Zero)); - Assert.That(second.ControlPoints[0].Type, Is.EqualTo(PathType.PERFECTCURVE)); + Assert.That(second.ControlPoints[0].Type, Is.EqualTo(PathType.PERFECT_CURVE)); Assert.That(second.ControlPoints[1].Position, Is.EqualTo(new Vector2(161, -244))); Assert.That(second.ControlPoints[1].Type, Is.EqualTo(null)); Assert.That(second.ControlPoints[2].Position, Is.EqualTo(new Vector2(376, -3))); @@ -904,12 +904,12 @@ namespace osu.Game.Tests.Beatmaps.Formats var seventh = ((IHasPath)decoded.HitObjects[6]).Path; Assert.That(seventh.ControlPoints[0].Position, Is.EqualTo(Vector2.Zero)); - Assert.That(seventh.ControlPoints[0].Type == PathType.PERFECTCURVE); + Assert.That(seventh.ControlPoints[0].Type == PathType.PERFECT_CURVE); Assert.That(seventh.ControlPoints[1].Position, Is.EqualTo(new Vector2(75, 145))); Assert.That(seventh.ControlPoints[1].Type == null); Assert.That(seventh.ControlPoints[2].Position, Is.EqualTo(new Vector2(170, 75))); - Assert.That(seventh.ControlPoints[2].Type == PathType.PERFECTCURVE); + Assert.That(seventh.ControlPoints[2].Type == PathType.PERFECT_CURVE); Assert.That(seventh.ControlPoints[3].Position, Is.EqualTo(new Vector2(300, 145))); Assert.That(seventh.ControlPoints[3].Type == null); Assert.That(seventh.ControlPoints[4].Position, Is.EqualTo(new Vector2(410, 20))); diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneBezierConverter.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneBezierConverter.cs index e2333011c7..27497f77be 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneBezierConverter.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneBezierConverter.cs @@ -143,7 +143,7 @@ namespace osu.Game.Tests.Visual.Gameplay { path.ControlPoints.AddRange(createSegment(PathType.LINEAR, Vector2.Zero, new Vector2(100, 0))); path.ControlPoints.AddRange(createSegment(PathType.BEZIER, new Vector2(100, 0), new Vector2(150, 30), new Vector2(100, 100))); - path.ControlPoints.AddRange(createSegment(PathType.PERFECTCURVE, new Vector2(100, 100), new Vector2(25, 50), Vector2.Zero)); + path.ControlPoints.AddRange(createSegment(PathType.PERFECT_CURVE, new Vector2(100, 100), new Vector2(25, 50), Vector2.Zero)); }); } @@ -159,7 +159,7 @@ namespace osu.Game.Tests.Visual.Gameplay { AddStep("create path", () => { - path.ControlPoints.AddRange(createSegment(PathType.PERFECTCURVE, Vector2.Zero, new Vector2(width / 2, height), new Vector2(width, 0))); + path.ControlPoints.AddRange(createSegment(PathType.PERFECT_CURVE, Vector2.Zero, new Vector2(width / 2, height), new Vector2(width, 0))); }); } @@ -172,11 +172,11 @@ namespace osu.Game.Tests.Visual.Gameplay switch (points) { case 2: - path.ControlPoints.AddRange(createSegment(PathType.PERFECTCURVE, Vector2.Zero, new Vector2(0, 100))); + path.ControlPoints.AddRange(createSegment(PathType.PERFECT_CURVE, Vector2.Zero, new Vector2(0, 100))); break; case 4: - path.ControlPoints.AddRange(createSegment(PathType.PERFECTCURVE, Vector2.Zero, new Vector2(0, 100), new Vector2(100), new Vector2(100, 0))); + path.ControlPoints.AddRange(createSegment(PathType.PERFECT_CURVE, Vector2.Zero, new Vector2(0, 100), new Vector2(100), new Vector2(100, 0))); break; } }); diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSliderPath.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSliderPath.cs index d44af45fe4..44a2e5fb9b 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSliderPath.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSliderPath.cs @@ -149,11 +149,11 @@ namespace osu.Game.Tests.Visual.Gameplay switch (points) { case 2: - path.ControlPoints.AddRange(createSegment(PathType.PERFECTCURVE, Vector2.Zero, new Vector2(0, 100))); + path.ControlPoints.AddRange(createSegment(PathType.PERFECT_CURVE, Vector2.Zero, new Vector2(0, 100))); break; case 4: - path.ControlPoints.AddRange(createSegment(PathType.PERFECTCURVE, Vector2.Zero, new Vector2(0, 100), new Vector2(100), new Vector2(100, 0))); + path.ControlPoints.AddRange(createSegment(PathType.PERFECT_CURVE, Vector2.Zero, new Vector2(0, 100), new Vector2(100), new Vector2(100, 0))); break; } }); diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs index ff446206ac..b375a6f7ff 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs @@ -437,7 +437,7 @@ namespace osu.Game.Beatmaps.Formats // Explicit segments have a new format in which the type is injected into the middle of the control point string. // To preserve compatibility with osu-stable as much as possible, explicit segments with the same type are converted to use implicit segments by duplicating the control point. // One exception are consecutive perfect curves, which aren't supported in osu!stable and can lead to decoding issues if encoded as implicit segments - bool needsExplicitSegment = point.Type != lastType || point.Type == PathType.PERFECTCURVE; + bool needsExplicitSegment = point.Type != lastType || point.Type == PathType.PERFECT_CURVE; // Another exception to this is when the last two control points of the last segment were duplicated. This is not a scenario supported by osu!stable. // Lazer does not add implicit segments for the last two control points of _any_ explicit segment, so an explicit segment is forced in order to maintain consistency with the decoder. @@ -453,25 +453,21 @@ namespace osu.Game.Beatmaps.Formats if (needsExplicitSegment) { - switch (point.Type) + switch (point.Type?.Type) { - case { Type: SplineType.BSpline, Degree: > 0 }: - writer.Write($"B{point.Type.Value.Degree}|"); + case SplineType.BSpline: + writer.Write(point.Type.Value.Degree > 0 ? $"B{point.Type.Value.Degree}|" : "B|"); break; - case { Type: SplineType.BSpline, Degree: <= 0 }: - writer.Write("B|"); - break; - - case { Type: SplineType.Catmull }: + case SplineType.Catmull: writer.Write("C|"); break; - case { Type: SplineType.PerfectCurve }: + case SplineType.PerfectCurve: writer.Write("P|"); break; - case { Type: SplineType.Linear }: + case SplineType.Linear: writer.Write("L|"); break; } diff --git a/osu.Game/Rulesets/Edit/ComposerDistanceSnapProvider.cs b/osu.Game/Rulesets/Edit/ComposerDistanceSnapProvider.cs index ddf539771d..68411d2b01 100644 --- a/osu.Game/Rulesets/Edit/ComposerDistanceSnapProvider.cs +++ b/osu.Game/Rulesets/Edit/ComposerDistanceSnapProvider.cs @@ -29,7 +29,7 @@ using osu.Game.Screens.Edit.Components.TernaryButtons; namespace osu.Game.Rulesets.Edit { - public abstract partial class ComposerDistanceSnapProvider : Component, IDistanceSnapProvider, IScrollBindingHandler + public abstract partial class ComposerDistanceSnapProvider : Component, IDistanceSnapProvider, IScrollBindingHandler, IToolboxAttachment { private const float adjust_step = 0.1f; diff --git a/osu.Game/Rulesets/Edit/IToolboxAttachment.cs b/osu.Game/Rulesets/Edit/IToolboxAttachment.cs new file mode 100644 index 0000000000..7d7c5980b2 --- /dev/null +++ b/osu.Game/Rulesets/Edit/IToolboxAttachment.cs @@ -0,0 +1,10 @@ +// 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.Edit +{ + public interface IToolboxAttachment + { + void AttachToToolbox(ExpandingToolboxContainer toolbox); + } +} diff --git a/osu.Game/Rulesets/Objects/BezierConverter.cs b/osu.Game/Rulesets/Objects/BezierConverter.cs index ed86fc10e0..5dc0839d37 100644 --- a/osu.Game/Rulesets/Objects/BezierConverter.cs +++ b/osu.Game/Rulesets/Objects/BezierConverter.cs @@ -126,9 +126,9 @@ namespace osu.Game.Rulesets.Objects var segmentVertices = vertices.AsSpan().Slice(start, i - start + 1); var segmentType = controlPoints[start].Type ?? PathType.LINEAR; - switch (segmentType) + switch (segmentType.Type) { - case { Type: SplineType.Catmull }: + case SplineType.Catmull: foreach (var segment in ConvertCatmullToBezierAnchors(segmentVertices)) { for (int j = 0; j < segment.Length - 1; j++) @@ -139,7 +139,7 @@ namespace osu.Game.Rulesets.Objects break; - case { Type: SplineType.Linear }: + case SplineType.Linear: foreach (var segment in ConvertLinearToBezierAnchors(segmentVertices)) { for (int j = 0; j < segment.Length - 1; j++) @@ -150,7 +150,7 @@ namespace osu.Game.Rulesets.Objects break; - case { Type: SplineType.PerfectCurve }: + case SplineType.PerfectCurve: var circleResult = ConvertCircleToBezierAnchors(segmentVertices); for (int j = 0; j < circleResult.Length - 1; j++) diff --git a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs index 6a13a897c4..92a92dca8f 100644 --- a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs @@ -224,19 +224,19 @@ namespace osu.Game.Rulesets.Objects.Legacy { default: case 'C': - return new PathType(SplineType.Catmull); + return PathType.CATMULL; case 'B': if (input.Length > 1 && int.TryParse(input.Substring(1), out int degree) && degree > 0) - return new PathType { Type = SplineType.BSpline, Degree = degree }; + return PathType.BSpline(degree); return new PathType(SplineType.BSpline); case 'L': - return new PathType(SplineType.Linear); + return PathType.LINEAR; case 'P': - return new PathType(SplineType.PerfectCurve); + return PathType.PERFECT_CURVE; } } @@ -323,7 +323,7 @@ namespace osu.Game.Rulesets.Objects.Legacy readPoint(endPoint, offset, out vertices[^1]); // Edge-case rules (to match stable). - if (type == PathType.PERFECTCURVE) + if (type == PathType.PERFECT_CURVE) { if (vertices.Length != 3) type = PathType.BEZIER; diff --git a/osu.Game/Rulesets/Objects/SliderPathExtensions.cs b/osu.Game/Rulesets/Objects/SliderPathExtensions.cs index d7e5e4574d..29b34ae4f0 100644 --- a/osu.Game/Rulesets/Objects/SliderPathExtensions.cs +++ b/osu.Game/Rulesets/Objects/SliderPathExtensions.cs @@ -53,7 +53,7 @@ namespace osu.Game.Rulesets.Objects inheritedLinearPoints.ForEach(p => p.Type = null); // Recalculate middle perfect curve control points at the end of the slider path. - if (controlPoints.Count >= 3 && controlPoints[^3].Type == PathType.PERFECTCURVE && controlPoints[^2].Type is null && segmentEnds.Any()) + if (controlPoints.Count >= 3 && controlPoints[^3].Type == PathType.PERFECT_CURVE && controlPoints[^2].Type is null && segmentEnds.Any()) { double lastSegmentStart = segmentEnds.Length > 1 ? segmentEnds[^2] : 0; double lastSegmentEnd = segmentEnds[^1]; diff --git a/osu.Game/Rulesets/Objects/Types/PathType.cs b/osu.Game/Rulesets/Objects/Types/PathType.cs index a6e8e173d4..4fb48bb8b4 100644 --- a/osu.Game/Rulesets/Objects/Types/PathType.cs +++ b/osu.Game/Rulesets/Objects/Types/PathType.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Diagnostics; namespace osu.Game.Rulesets.Objects.Types { @@ -14,12 +13,12 @@ namespace osu.Game.Rulesets.Objects.Types PerfectCurve } - public readonly struct PathType + public readonly struct PathType : IEquatable { public static readonly PathType CATMULL = new PathType(SplineType.Catmull); public static readonly PathType BEZIER = new PathType(SplineType.BSpline); public static readonly PathType LINEAR = new PathType(SplineType.Linear); - public static readonly PathType PERFECTCURVE = new PathType(SplineType.PerfectCurve); + public static readonly PathType PERFECT_CURVE = new PathType(SplineType.PerfectCurve); /// /// The type of the spline that should be used to interpret the control points of the path. @@ -52,8 +51,13 @@ namespace osu.Game.Rulesets.Objects.Types public static PathType BSpline(int degree) { - Debug.Assert(degree > 0); + if (degree <= 0) + throw new ArgumentOutOfRangeException(nameof(degree), "The degree of a B-Spline path must be greater than zero."); + return new PathType { Type = SplineType.BSpline, Degree = degree }; } + + public bool Equals(PathType other) + => Type == other.Type && Degree == other.Degree; } } diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index fc10f90e8f..8d42cd22e3 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -37,7 +37,7 @@ - + From 90ec6895d100dea67e282c75a57779ffa87d0f5d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Mu=CC=88ller-Ho=CC=88hne?= Date: Tue, 14 Nov 2023 16:52:45 +0900 Subject: [PATCH 27/91] Automatic red control point generation & corner threshold --- .../Sliders/SliderPlacementBlueprint.cs | 28 +++++++++-- .../Edit/ISliderDrawingSettingsProvider.cs | 1 + .../Edit/OsuSliderDrawingSettingsProvider.cs | 46 +++++++++++++++++-- 3 files changed, 65 insertions(+), 10 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs index c722f0fdbc..69e2a40689 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs @@ -86,6 +86,13 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders bSplineBuilder.Tolerance = e.NewValue; updateSliderPathFromBSplineBuilder(); }, true); + + drawingSettingsProvider.CornerThreshold.BindValueChanged(e => + { + if (bSplineBuilder.CornerThreshold != e.NewValue) + bSplineBuilder.CornerThreshold = e.NewValue; + updateSliderPathFromBSplineBuilder(); + }, true); } [Resolved] @@ -100,8 +107,9 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders case SliderPlacementState.Initial: BeginPlacement(); - double? nearestSliderVelocity = (editorBeatmap.HitObjects - .LastOrDefault(h => h is Slider && h.GetEndTime() < HitObject.StartTime) as Slider)?.SliderVelocityMultiplier; + double? nearestSliderVelocity = (editorBeatmap + .HitObjects + .LastOrDefault(h => h is Slider && h.GetEndTime() < HitObject.StartTime) as Slider)?.SliderVelocityMultiplier; HitObject.SliderVelocityMultiplier = nearestSliderVelocity ?? 1; HitObject.Position = ToLocalSpace(result.ScreenSpacePosition); @@ -193,9 +201,19 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders { Scheduler.AddOnce(static self => { - var cps = self.bSplineBuilder.GetControlPoints(); - self.HitObject.Path.ControlPoints.RemoveRange(1, self.HitObject.Path.ControlPoints.Count - 1); - self.HitObject.Path.ControlPoints.AddRange(cps.Skip(1).Select(v => new PathControlPoint(v))); + var cps = self.bSplineBuilder.ControlPoints; + var sliderCps = self.HitObject.Path.ControlPoints; + sliderCps.RemoveRange(1, sliderCps.Count - 1); + + // Add the control points from the BSpline builder while converting control points that repeat + // three or more times to a single PathControlPoint with linear type. + for (int i = 1; i < cps.Count; i++) + { + bool isSharp = i < cps.Count - 2 && cps[i] == cps[i + 1] && cps[i] == cps[i + 2]; + sliderCps.Add(new PathControlPoint(cps[i], isSharp ? PathType.BSpline(3) : null)); + if (isSharp) + i += 2; + } }, this); } diff --git a/osu.Game.Rulesets.Osu/Edit/ISliderDrawingSettingsProvider.cs b/osu.Game.Rulesets.Osu/Edit/ISliderDrawingSettingsProvider.cs index 1138588259..31ed98e1dd 100644 --- a/osu.Game.Rulesets.Osu/Edit/ISliderDrawingSettingsProvider.cs +++ b/osu.Game.Rulesets.Osu/Edit/ISliderDrawingSettingsProvider.cs @@ -8,5 +8,6 @@ namespace osu.Game.Rulesets.Osu.Edit public interface ISliderDrawingSettingsProvider { BindableFloat Tolerance { get; } + BindableFloat CornerThreshold { get; } } } diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSliderDrawingSettingsProvider.cs b/osu.Game.Rulesets.Osu/Edit/OsuSliderDrawingSettingsProvider.cs index 643732d90a..1fe1326f38 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuSliderDrawingSettingsProvider.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuSliderDrawingSettingsProvider.cs @@ -12,20 +12,34 @@ namespace osu.Game.Rulesets.Osu.Edit { public partial class OsuSliderDrawingSettingsProvider : Drawable, ISliderDrawingSettingsProvider, IToolboxAttachment { - public BindableFloat Tolerance { get; } = new BindableFloat(0.1f) + public BindableFloat Tolerance { get; } = new BindableFloat(1.5f) + { + MinValue = 0.05f, + MaxValue = 3f, + Precision = 0.01f + }; + + private readonly BindableInt sliderTolerance = new BindableInt(50) + { + MinValue = 5, + MaxValue = 100 + }; + + public BindableFloat CornerThreshold { get; } = new BindableFloat(0.4f) { MinValue = 0.05f, MaxValue = 1f, Precision = 0.01f }; - private readonly BindableInt sliderTolerance = new BindableInt(10) + private readonly BindableInt sliderCornerThreshold = new BindableInt(40) { MinValue = 5, MaxValue = 100 }; private ExpandableSlider toleranceSlider = null!; + private ExpandableSlider cornerThresholdSlider = null!; protected override void LoadComplete() { @@ -33,16 +47,28 @@ namespace osu.Game.Rulesets.Osu.Edit sliderTolerance.BindValueChanged(v => { - float newValue = v.NewValue / 100f; - if (!Precision.AlmostEquals(newValue, Tolerance.Value)) + float newValue = v.NewValue / 33f; + if (!Precision.AlmostEquals(newValue, Tolerance.Value, 1e-7f)) Tolerance.Value = newValue; }); Tolerance.BindValueChanged(v => { - int newValue = (int)Math.Round(v.NewValue * 100f); + int newValue = (int)Math.Round(v.NewValue * 33f); if (sliderTolerance.Value != newValue) sliderTolerance.Value = newValue; }); + sliderCornerThreshold.BindValueChanged(v => + { + float newValue = v.NewValue / 100f; + if (!Precision.AlmostEquals(newValue, CornerThreshold.Value, 1e-7f)) + CornerThreshold.Value = newValue; + }); + CornerThreshold.BindValueChanged(v => + { + int newValue = (int)Math.Round(v.NewValue * 100f); + if (sliderCornerThreshold.Value != newValue) + sliderCornerThreshold.Value = newValue; + }); } public void AttachToToolbox(ExpandingToolboxContainer toolboxContainer) @@ -54,6 +80,10 @@ namespace osu.Game.Rulesets.Osu.Edit toleranceSlider = new ExpandableSlider { Current = sliderTolerance + }, + cornerThresholdSlider = new ExpandableSlider + { + Current = sliderCornerThreshold } } }); @@ -63,6 +93,12 @@ namespace osu.Game.Rulesets.Osu.Edit toleranceSlider.ContractedLabelText = $"C. P. S.: {e.NewValue:N0}"; toleranceSlider.ExpandedLabelText = $"Control Point Spacing: {e.NewValue:N0}"; }, true); + + sliderCornerThreshold.BindValueChanged(e => + { + cornerThresholdSlider.ContractedLabelText = $"C. T.: {e.NewValue:N0}"; + cornerThresholdSlider.ExpandedLabelText = $"Corner Threshold: {e.NewValue:N0}"; + }, true); } } } From ceeaf5b67c527c787b8d34eb3594207ed9ac14d7 Mon Sep 17 00:00:00 2001 From: cs Date: Wed, 15 Nov 2023 07:09:33 +0100 Subject: [PATCH 28/91] CI fixes and small tweaks --- .../Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs | 4 ++-- .../Edit/OsuSliderDrawingSettingsProvider.cs | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs index 69e2a40689..df7d2c716b 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs @@ -108,8 +108,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders BeginPlacement(); double? nearestSliderVelocity = (editorBeatmap - .HitObjects - .LastOrDefault(h => h is Slider && h.GetEndTime() < HitObject.StartTime) as Slider)?.SliderVelocityMultiplier; + .HitObjects + .LastOrDefault(h => h is Slider && h.GetEndTime() < HitObject.StartTime) as Slider)?.SliderVelocityMultiplier; HitObject.SliderVelocityMultiplier = nearestSliderVelocity ?? 1; HitObject.Position = ToLocalSpace(result.ScreenSpacePosition); diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSliderDrawingSettingsProvider.cs b/osu.Game.Rulesets.Osu/Edit/OsuSliderDrawingSettingsProvider.cs index 1fe1326f38..4326b2e943 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuSliderDrawingSettingsProvider.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuSliderDrawingSettingsProvider.cs @@ -48,7 +48,7 @@ namespace osu.Game.Rulesets.Osu.Edit sliderTolerance.BindValueChanged(v => { float newValue = v.NewValue / 33f; - if (!Precision.AlmostEquals(newValue, Tolerance.Value, 1e-7f)) + if (!Precision.AlmostEquals(newValue, Tolerance.Value)) Tolerance.Value = newValue; }); Tolerance.BindValueChanged(v => @@ -60,7 +60,7 @@ namespace osu.Game.Rulesets.Osu.Edit sliderCornerThreshold.BindValueChanged(v => { float newValue = v.NewValue / 100f; - if (!Precision.AlmostEquals(newValue, CornerThreshold.Value, 1e-7f)) + if (!Precision.AlmostEquals(newValue, CornerThreshold.Value)) CornerThreshold.Value = newValue; }); CornerThreshold.BindValueChanged(v => @@ -73,7 +73,7 @@ namespace osu.Game.Rulesets.Osu.Edit public void AttachToToolbox(ExpandingToolboxContainer toolboxContainer) { - toolboxContainer.Add(new EditorToolboxGroup("drawing") + toolboxContainer.Add(new EditorToolboxGroup("slider") { Children = new Drawable[] { From 520642975bda8de23c51e2b350c637a93115b08c Mon Sep 17 00:00:00 2001 From: cs Date: Wed, 15 Nov 2023 07:45:09 +0100 Subject: [PATCH 29/91] Fix control point hover text and context menu --- .../Sliders/Components/PathControlPointPiece.cs | 2 +- .../Sliders/Components/PathControlPointVisualiser.cs | 2 +- osu.Game/Rulesets/Objects/Types/PathType.cs | 12 +++++++++++- 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs index ac9048d5c7..03792d8520 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs @@ -279,6 +279,6 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components } } - public LocalisableString TooltipText => ControlPoint.Type.ToString() ?? string.Empty; + public LocalisableString TooltipText => ControlPoint.Type?.Description ?? string.Empty; } } diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs index 5ab050ed48..faae966d02 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs @@ -403,7 +403,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components int totalCount = Pieces.Count(p => p.IsSelected.Value); int countOfState = Pieces.Where(p => p.IsSelected.Value).Count(p => p.ControlPoint.Type == type); - var item = new TernaryStateRadioMenuItem(type == null ? "Inherit" : type.ToString().Humanize(), MenuItemType.Standard, _ => + var item = new TernaryStateRadioMenuItem(type == null ? "Inherit" : type!.Value.Description, MenuItemType.Standard, _ => { foreach (var p in Pieces.Where(p => p.IsSelected.Value)) updatePathType(p, type); diff --git a/osu.Game/Rulesets/Objects/Types/PathType.cs b/osu.Game/Rulesets/Objects/Types/PathType.cs index 4fb48bb8b4..e4249154e5 100644 --- a/osu.Game/Rulesets/Objects/Types/PathType.cs +++ b/osu.Game/Rulesets/Objects/Types/PathType.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using osu.Framework.Bindables; namespace osu.Game.Rulesets.Objects.Types { @@ -13,7 +14,7 @@ namespace osu.Game.Rulesets.Objects.Types PerfectCurve } - public readonly struct PathType : IEquatable + public readonly struct PathType : IEquatable, IHasDescription { public static readonly PathType CATMULL = new PathType(SplineType.Catmull); public static readonly PathType BEZIER = new PathType(SplineType.BSpline); @@ -31,6 +32,15 @@ namespace osu.Game.Rulesets.Objects.Types /// public int? Degree { get; init; } + public string Description => Type switch + { + SplineType.Catmull => "Catmull", + SplineType.BSpline => Degree == null ? "Bezier" : "B-Spline", + SplineType.Linear => "Linear", + SplineType.PerfectCurve => "Perfect Curve", + _ => Type.ToString() + }; + public PathType(SplineType splineType) { Type = splineType; From 360864fd7b6dac30bda9a2cf4092a316b280427d Mon Sep 17 00:00:00 2001 From: cs Date: Wed, 15 Nov 2023 07:45:28 +0100 Subject: [PATCH 30/91] Hide catmull curve type when possible --- .../Sliders/Components/PathControlPointVisualiser.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs index faae966d02..95c72a0a62 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs @@ -371,7 +371,11 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components curveTypeItems.Add(createMenuItemForPathType(PathType.PERFECT_CURVE)); curveTypeItems.Add(createMenuItemForPathType(PathType.BEZIER)); curveTypeItems.Add(createMenuItemForPathType(PathType.BSpline(3))); - curveTypeItems.Add(createMenuItemForPathType(PathType.CATMULL)); + + var hoveredPiece = Pieces.FirstOrDefault(p => p.IsHovered); + + if (hoveredPiece?.ControlPoint.Type == PathType.CATMULL) + curveTypeItems.Add(createMenuItemForPathType(PathType.CATMULL)); var menuItems = new List { From a1673160f12e08612aa42a9db89f8b9f2e6324f0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 17 Nov 2023 16:44:11 +0900 Subject: [PATCH 31/91] Refactor `OsuAutoGenerator` to allow custom SPM specifications --- osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs b/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs index 5a3d882ef0..acce8c03e8 100644 --- a/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs +++ b/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs @@ -339,6 +339,10 @@ namespace osu.Game.Rulesets.Osu.Replays AddFrameToReplay(startFrame); + // ~477 as per stable. + const float spin_rpm = 60000f / 20 * (180 / MathF.PI) / 360; + float radsPerMillisecond = MathUtils.DegreesToRadians(spin_rpm * 360) / 60000; + switch (h) { // We add intermediate frames for spinning / following a slider here. @@ -354,7 +358,7 @@ namespace osu.Game.Rulesets.Osu.Replays for (double nextFrame = h.StartTime + GetFrameDelay(h.StartTime); nextFrame < spinner.EndTime; nextFrame += GetFrameDelay(nextFrame)) { t = ApplyModsToTimeDelta(previousFrame, nextFrame) * spinnerDirection; - angle += (float)t / 20; + angle += (float)t * radsPerMillisecond; Vector2 pos = SPINNER_CENTRE + CirclePosition(angle, SPIN_RADIUS); AddFrameToReplay(new OsuReplayFrame((int)nextFrame, new Vector2(pos.X, pos.Y), action)); @@ -363,7 +367,7 @@ namespace osu.Game.Rulesets.Osu.Replays } t = ApplyModsToTimeDelta(previousFrame, spinner.EndTime) * spinnerDirection; - angle += (float)t / 20; + angle += (float)t * radsPerMillisecond; Vector2 endPosition = SPINNER_CENTRE + CirclePosition(angle, SPIN_RADIUS); From 66f7b9fae1e0aaf86609bdbf3315870198e0f8b2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 17 Nov 2023 19:09:24 +0900 Subject: [PATCH 32/91] Adjust slider follow circle animation to not abruptly scale on early ticks --- .../Skinning/Argon/ArgonFollowCircle.cs | 9 ++++++--- .../Skinning/Default/DefaultFollowCircle.cs | 9 ++++++--- .../Skinning/Legacy/LegacyFollowCircle.cs | 7 +++++-- 3 files changed, 17 insertions(+), 8 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonFollowCircle.cs b/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonFollowCircle.cs index fca3e70236..ea21d71d5f 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonFollowCircle.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonFollowCircle.cs @@ -88,9 +88,12 @@ namespace osu.Game.Rulesets.Osu.Skinning.Argon protected override void OnSliderTick() { - this.ScaleTo(DrawableSliderBall.FOLLOW_AREA * 1.08f, 40, Easing.OutQuint) - .Then() - .ScaleTo(DrawableSliderBall.FOLLOW_AREA, 200f, Easing.OutQuint); + if (Scale.X >= DrawableSliderBall.FOLLOW_AREA * 0.98f) + { + this.ScaleTo(DrawableSliderBall.FOLLOW_AREA * 1.08f, 40, Easing.OutQuint) + .Then() + .ScaleTo(DrawableSliderBall.FOLLOW_AREA, 200f, Easing.OutQuint); + } } protected override void OnSliderBreak() diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/DefaultFollowCircle.cs b/osu.Game.Rulesets.Osu/Skinning/Default/DefaultFollowCircle.cs index 3c41d473f4..4adbfc3928 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Default/DefaultFollowCircle.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Default/DefaultFollowCircle.cs @@ -59,9 +59,12 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default protected override void OnSliderTick() { - this.ScaleTo(DrawableSliderBall.FOLLOW_AREA * 1.08f, 40, Easing.OutQuint) - .Then() - .ScaleTo(DrawableSliderBall.FOLLOW_AREA, 200f, Easing.OutQuint); + if (Scale.X >= DrawableSliderBall.FOLLOW_AREA * 0.98f) + { + this.ScaleTo(DrawableSliderBall.FOLLOW_AREA * 1.08f, 40, Easing.OutQuint) + .Then() + .ScaleTo(DrawableSliderBall.FOLLOW_AREA, 200f, Easing.OutQuint); + } } protected override void OnSliderBreak() diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyFollowCircle.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyFollowCircle.cs index f8dcb9e8a2..fa2bb9b2ad 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyFollowCircle.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyFollowCircle.cs @@ -44,8 +44,11 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy protected override void OnSliderTick() { - this.ScaleTo(2.2f) - .ScaleTo(2f, 200); + if (Scale.X >= 2f) + { + this.ScaleTo(2.2f) + .ScaleTo(2f, 200); + } } protected override void OnSliderBreak() From 307ec172cbcc49f0edf26aed318d5390666faafa Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 17 Nov 2023 23:48:48 +0900 Subject: [PATCH 33/91] Use simplified formula --- osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs b/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs index acce8c03e8..9d63949dcc 100644 --- a/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs +++ b/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs @@ -340,7 +340,7 @@ namespace osu.Game.Rulesets.Osu.Replays AddFrameToReplay(startFrame); // ~477 as per stable. - const float spin_rpm = 60000f / 20 * (180 / MathF.PI) / 360; + const float spin_rpm = 0.05f / (2 * MathF.PI) * 60000; float radsPerMillisecond = MathUtils.DegreesToRadians(spin_rpm * 360) / 60000; switch (h) From e182acf3e8c012fe36f8317d70ca7d6abe1803e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 20 Nov 2023 11:50:28 +0900 Subject: [PATCH 34/91] Expand comment for clarification --- osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs b/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs index 9d63949dcc..1cf6bc91f0 100644 --- a/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs +++ b/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs @@ -339,7 +339,8 @@ namespace osu.Game.Rulesets.Osu.Replays AddFrameToReplay(startFrame); - // ~477 as per stable. + // 0.05 rad/ms, or ~477 RPM, as per stable. + // the redundant conversion from RPM to rad/ms is here for ease of testing custom SPM specs. const float spin_rpm = 0.05f / (2 * MathF.PI) * 60000; float radsPerMillisecond = MathUtils.DegreesToRadians(spin_rpm * 360) / 60000; From 33b592f1c723fb4824f2084337de9dbb940d2b1f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 20 Nov 2023 12:04:30 +0900 Subject: [PATCH 35/91] Update framework (again) --- osu.Android.props | 2 +- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index 1f6a65c450..dd5a8996fb 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -10,7 +10,7 @@ true - + diff --git a/osu.iOS.props b/osu.iOS.props index 70525a5c59..9a0832b4e7 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -23,6 +23,6 @@ iossimulator-x64 - + From 577cb9994ce54202bf2c07548389f0e6b701fa8f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 20 Nov 2023 12:24:32 +0900 Subject: [PATCH 36/91] Move static instances / construction methods closer together --- osu.Game/Rulesets/Objects/Types/PathType.cs | 38 ++++++++++----------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/osu.Game/Rulesets/Objects/Types/PathType.cs b/osu.Game/Rulesets/Objects/Types/PathType.cs index e4249154e5..0e3adb4473 100644 --- a/osu.Game/Rulesets/Objects/Types/PathType.cs +++ b/osu.Game/Rulesets/Objects/Types/PathType.cs @@ -16,11 +16,6 @@ namespace osu.Game.Rulesets.Objects.Types public readonly struct PathType : IEquatable, IHasDescription { - public static readonly PathType CATMULL = new PathType(SplineType.Catmull); - public static readonly PathType BEZIER = new PathType(SplineType.BSpline); - public static readonly PathType LINEAR = new PathType(SplineType.Linear); - public static readonly PathType PERFECT_CURVE = new PathType(SplineType.PerfectCurve); - /// /// The type of the spline that should be used to interpret the control points of the path. /// @@ -32,6 +27,25 @@ namespace osu.Game.Rulesets.Objects.Types /// public int? Degree { get; init; } + public PathType(SplineType splineType) + { + Type = splineType; + Degree = null; + } + + public static readonly PathType CATMULL = new PathType(SplineType.Catmull); + public static readonly PathType BEZIER = new PathType(SplineType.BSpline); + public static readonly PathType LINEAR = new PathType(SplineType.Linear); + public static readonly PathType PERFECT_CURVE = new PathType(SplineType.PerfectCurve); + + public static PathType BSpline(int degree) + { + if (degree <= 0) + throw new ArgumentOutOfRangeException(nameof(degree), "The degree of a B-Spline path must be greater than zero."); + + return new PathType { Type = SplineType.BSpline, Degree = degree }; + } + public string Description => Type switch { SplineType.Catmull => "Catmull", @@ -41,12 +55,6 @@ namespace osu.Game.Rulesets.Objects.Types _ => Type.ToString() }; - public PathType(SplineType splineType) - { - Type = splineType; - Degree = null; - } - public override int GetHashCode() => HashCode.Combine(Type, Degree); @@ -59,14 +67,6 @@ namespace osu.Game.Rulesets.Objects.Types public static bool operator !=(PathType a, PathType b) => a.Type != b.Type || a.Degree != b.Degree; - public static PathType BSpline(int degree) - { - if (degree <= 0) - throw new ArgumentOutOfRangeException(nameof(degree), "The degree of a B-Spline path must be greater than zero."); - - return new PathType { Type = SplineType.BSpline, Degree = degree }; - } - public bool Equals(PathType other) => Type == other.Type && Degree == other.Degree; } From 25c1a900473de216ba5c07b306b4e49316ec7828 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 20 Nov 2023 12:25:44 +0900 Subject: [PATCH 37/91] Change switchexpr to standard switch statement --- osu.Game/Rulesets/Objects/Types/PathType.cs | 29 ++++++++++++++++----- 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/osu.Game/Rulesets/Objects/Types/PathType.cs b/osu.Game/Rulesets/Objects/Types/PathType.cs index 0e3adb4473..95ddcb8b05 100644 --- a/osu.Game/Rulesets/Objects/Types/PathType.cs +++ b/osu.Game/Rulesets/Objects/Types/PathType.cs @@ -46,14 +46,29 @@ namespace osu.Game.Rulesets.Objects.Types return new PathType { Type = SplineType.BSpline, Degree = degree }; } - public string Description => Type switch + public string Description { - SplineType.Catmull => "Catmull", - SplineType.BSpline => Degree == null ? "Bezier" : "B-Spline", - SplineType.Linear => "Linear", - SplineType.PerfectCurve => "Perfect Curve", - _ => Type.ToString() - }; + get + { + switch (Type) + { + case SplineType.Catmull: + return "Catmull"; + + case SplineType.BSpline: + return Degree == null ? "Bezier" : "B-Spline"; + + case SplineType.Linear: + return "Linear"; + + case SplineType.PerfectCurve: + return "Perfect Curve"; + + default: + return Type.ToString(); + } + } + } public override int GetHashCode() => HashCode.Combine(Type, Degree); From 7820c8ce4d558381f80444fcf3fb38035382a852 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 20 Nov 2023 12:28:20 +0900 Subject: [PATCH 38/91] Decrease redundancy of equality implementations --- osu.Game/Rulesets/Objects/Types/PathType.cs | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/osu.Game/Rulesets/Objects/Types/PathType.cs b/osu.Game/Rulesets/Objects/Types/PathType.cs index 95ddcb8b05..37c1d0ab50 100644 --- a/osu.Game/Rulesets/Objects/Types/PathType.cs +++ b/osu.Game/Rulesets/Objects/Types/PathType.cs @@ -74,15 +74,12 @@ namespace osu.Game.Rulesets.Objects.Types => HashCode.Combine(Type, Degree); public override bool Equals(object? obj) - => obj is PathType pathType && this == pathType; - - public static bool operator ==(PathType a, PathType b) - => a.Type == b.Type && a.Degree == b.Degree; - - public static bool operator !=(PathType a, PathType b) - => a.Type != b.Type || a.Degree != b.Degree; + => obj is PathType pathType && Equals(pathType); public bool Equals(PathType other) => Type == other.Type && Degree == other.Degree; + + public static bool operator ==(PathType a, PathType b) => a.Equals(b); + public static bool operator !=(PathType a, PathType b) => !a.Equals(b); } } From 518dcc567b14cdf8ade2d449b20cdcbcfcf45e98 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 20 Nov 2023 12:41:22 +0900 Subject: [PATCH 39/91] Null-check `drawingSettingsProvider` As it's annotated as an optional dependency. --- .../Sliders/SliderPlacementBlueprint.cs | 25 +++++++++++-------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs index df7d2c716b..9b24415604 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs @@ -80,19 +80,22 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders base.LoadComplete(); inputManager = GetContainingInputManager(); - drawingSettingsProvider.Tolerance.BindValueChanged(e => + if (drawingSettingsProvider != null) { - if (bSplineBuilder.Tolerance != e.NewValue) - bSplineBuilder.Tolerance = e.NewValue; - updateSliderPathFromBSplineBuilder(); - }, true); + drawingSettingsProvider.Tolerance.BindValueChanged(e => + { + if (bSplineBuilder.Tolerance != e.NewValue) + bSplineBuilder.Tolerance = e.NewValue; + updateSliderPathFromBSplineBuilder(); + }, true); - drawingSettingsProvider.CornerThreshold.BindValueChanged(e => - { - if (bSplineBuilder.CornerThreshold != e.NewValue) - bSplineBuilder.CornerThreshold = e.NewValue; - updateSliderPathFromBSplineBuilder(); - }, true); + drawingSettingsProvider.CornerThreshold.BindValueChanged(e => + { + if (bSplineBuilder.CornerThreshold != e.NewValue) + bSplineBuilder.CornerThreshold = e.NewValue; + updateSliderPathFromBSplineBuilder(); + }, true); + } } [Resolved] From f46945a29439716ef10e5e82277cf5e338044094 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 20 Nov 2023 12:42:16 +0900 Subject: [PATCH 40/91] Avoid one unnecessary path update from B-spline builder --- .../Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs index 9b24415604..50b4377ccd 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs @@ -87,7 +87,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders if (bSplineBuilder.Tolerance != e.NewValue) bSplineBuilder.Tolerance = e.NewValue; updateSliderPathFromBSplineBuilder(); - }, true); + }); drawingSettingsProvider.CornerThreshold.BindValueChanged(e => { From 831884a64b74113f76bc59eed0466f232b71f7aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 20 Nov 2023 13:00:12 +0900 Subject: [PATCH 41/91] Remove unused enum member --- .../Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs index 50b4377ccd..50abcf1233 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs @@ -344,8 +344,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders { Initial, ControlPoints, - Drawing, - DrawingFinalization + Drawing } } } From affef85f2521246412949531e291fdbed4cf1272 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 20 Nov 2023 13:02:51 +0900 Subject: [PATCH 42/91] Remove `ISliderDrawingSettingsProvider` Seems like excessive abstraction. --- .../Blueprints/Sliders/SliderPlacementBlueprint.cs | 2 +- .../Edit/ISliderDrawingSettingsProvider.cs | 13 ------------- osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs | 2 +- .../Edit/OsuSliderDrawingSettingsProvider.cs | 2 +- 4 files changed, 3 insertions(+), 16 deletions(-) delete mode 100644 osu.Game.Rulesets.Osu/Edit/ISliderDrawingSettingsProvider.cs diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs index 50abcf1233..fb2c1d5149 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs @@ -46,7 +46,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders private IDistanceSnapProvider distanceSnapProvider { get; set; } [Resolved(CanBeNull = true)] - private ISliderDrawingSettingsProvider drawingSettingsProvider { get; set; } + private OsuSliderDrawingSettingsProvider drawingSettingsProvider { get; set; } private readonly IncrementalBSplineBuilder bSplineBuilder = new IncrementalBSplineBuilder(); diff --git a/osu.Game.Rulesets.Osu/Edit/ISliderDrawingSettingsProvider.cs b/osu.Game.Rulesets.Osu/Edit/ISliderDrawingSettingsProvider.cs deleted file mode 100644 index 31ed98e1dd..0000000000 --- a/osu.Game.Rulesets.Osu/Edit/ISliderDrawingSettingsProvider.cs +++ /dev/null @@ -1,13 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using osu.Framework.Bindables; - -namespace osu.Game.Rulesets.Osu.Edit -{ - public interface ISliderDrawingSettingsProvider - { - BindableFloat Tolerance { get; } - BindableFloat CornerThreshold { get; } - } -} diff --git a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs index d958b558cf..0dc0d6fd31 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs @@ -63,7 +63,7 @@ namespace osu.Game.Rulesets.Osu.Edit [Cached(typeof(IDistanceSnapProvider))] protected readonly OsuDistanceSnapProvider DistanceSnapProvider = new OsuDistanceSnapProvider(); - [Cached(typeof(ISliderDrawingSettingsProvider))] + [Cached] protected readonly OsuSliderDrawingSettingsProvider SliderDrawingSettingsProvider = new OsuSliderDrawingSettingsProvider(); [BackgroundDependencyLoader] diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSliderDrawingSettingsProvider.cs b/osu.Game.Rulesets.Osu/Edit/OsuSliderDrawingSettingsProvider.cs index 4326b2e943..0126b366d5 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuSliderDrawingSettingsProvider.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuSliderDrawingSettingsProvider.cs @@ -10,7 +10,7 @@ using osu.Game.Rulesets.Edit; namespace osu.Game.Rulesets.Osu.Edit { - public partial class OsuSliderDrawingSettingsProvider : Drawable, ISliderDrawingSettingsProvider, IToolboxAttachment + public partial class OsuSliderDrawingSettingsProvider : Drawable, IToolboxAttachment { public BindableFloat Tolerance { get; } = new BindableFloat(1.5f) { From 5d1bac6d7a55a980e608fbf6bfa8744ceb30b18f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 20 Nov 2023 13:17:43 +0900 Subject: [PATCH 43/91] Remove `IToolboxAttachment` for now The interface doesn't really do anything useful right now because it enforces a common contract, but all usages of the contract go through the concrete implementation, and it inflates the already-huge diff. --- .../Edit/OsuSliderDrawingSettingsProvider.cs | 2 +- osu.Game/Rulesets/Edit/ComposerDistanceSnapProvider.cs | 2 +- osu.Game/Rulesets/Edit/IToolboxAttachment.cs | 10 ---------- 3 files changed, 2 insertions(+), 12 deletions(-) delete mode 100644 osu.Game/Rulesets/Edit/IToolboxAttachment.cs diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSliderDrawingSettingsProvider.cs b/osu.Game.Rulesets.Osu/Edit/OsuSliderDrawingSettingsProvider.cs index 0126b366d5..e44f0265c8 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuSliderDrawingSettingsProvider.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuSliderDrawingSettingsProvider.cs @@ -10,7 +10,7 @@ using osu.Game.Rulesets.Edit; namespace osu.Game.Rulesets.Osu.Edit { - public partial class OsuSliderDrawingSettingsProvider : Drawable, IToolboxAttachment + public partial class OsuSliderDrawingSettingsProvider : Drawable { public BindableFloat Tolerance { get; } = new BindableFloat(1.5f) { diff --git a/osu.Game/Rulesets/Edit/ComposerDistanceSnapProvider.cs b/osu.Game/Rulesets/Edit/ComposerDistanceSnapProvider.cs index 68411d2b01..ddf539771d 100644 --- a/osu.Game/Rulesets/Edit/ComposerDistanceSnapProvider.cs +++ b/osu.Game/Rulesets/Edit/ComposerDistanceSnapProvider.cs @@ -29,7 +29,7 @@ using osu.Game.Screens.Edit.Components.TernaryButtons; namespace osu.Game.Rulesets.Edit { - public abstract partial class ComposerDistanceSnapProvider : Component, IDistanceSnapProvider, IScrollBindingHandler, IToolboxAttachment + public abstract partial class ComposerDistanceSnapProvider : Component, IDistanceSnapProvider, IScrollBindingHandler { private const float adjust_step = 0.1f; diff --git a/osu.Game/Rulesets/Edit/IToolboxAttachment.cs b/osu.Game/Rulesets/Edit/IToolboxAttachment.cs deleted file mode 100644 index 7d7c5980b2..0000000000 --- a/osu.Game/Rulesets/Edit/IToolboxAttachment.cs +++ /dev/null @@ -1,10 +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.Edit -{ - public interface IToolboxAttachment - { - void AttachToToolbox(ExpandingToolboxContainer toolbox); - } -} From 487326a4c786aa89b597ea191ec83c701386736d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 20 Nov 2023 13:22:36 +0900 Subject: [PATCH 44/91] Remove pattern matching syntax usage in switch Also throw on unknown types. --- osu.Game/Rulesets/Objects/BezierConverter.cs | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/osu.Game/Rulesets/Objects/BezierConverter.cs b/osu.Game/Rulesets/Objects/BezierConverter.cs index 5dc0839d37..4a68161899 100644 --- a/osu.Game/Rulesets/Objects/BezierConverter.cs +++ b/osu.Game/Rulesets/Objects/BezierConverter.cs @@ -70,21 +70,21 @@ namespace osu.Game.Rulesets.Objects var segmentVertices = vertices.AsSpan().Slice(start, i - start + 1); var segmentType = controlPoints[start].Type ?? PathType.LINEAR; - switch (segmentType) + switch (segmentType.Type) { - case { Type: SplineType.Catmull }: + case SplineType.Catmull: result.AddRange(from segment in ConvertCatmullToBezierAnchors(segmentVertices) from v in segment select v + position); break; - case { Type: SplineType.Linear }: + case SplineType.Linear: result.AddRange(from segment in ConvertLinearToBezierAnchors(segmentVertices) from v in segment select v + position); break; - case { Type: SplineType.PerfectCurve }: + case SplineType.PerfectCurve: result.AddRange(ConvertCircleToBezierAnchors(segmentVertices).Select(v => v + position)); break; - default: + case SplineType.BSpline: if (segmentType.Degree != null) throw new NotImplementedException("BSpline conversion of arbitrary degree is not implemented."); @@ -94,6 +94,9 @@ namespace osu.Game.Rulesets.Objects } break; + + default: + throw new ArgumentOutOfRangeException(nameof(segmentType.Type), segmentType.Type, "Unsupported segment type found when converting to legacy Bezier"); } // Start the new segment at the current vertex @@ -160,13 +163,16 @@ namespace osu.Game.Rulesets.Objects break; - default: + case SplineType.BSpline: for (int j = 0; j < segmentVertices.Length - 1; j++) { result.Add(new PathControlPoint(segmentVertices[j], j == 0 ? segmentType : null)); } break; + + default: + throw new ArgumentOutOfRangeException(nameof(segmentType.Type), segmentType.Type, "Unsupported segment type found when converting to legacy Bezier"); } // Start the new segment at the current vertex From 80a3225bb28f315aeceda4dc5e10c2222faa0e1f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 20 Nov 2023 13:35:07 +0900 Subject: [PATCH 45/91] Use static `BEZIER` instead of allocating new every time --- osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs index 92a92dca8f..411a9b0d63 100644 --- a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs @@ -230,7 +230,7 @@ namespace osu.Game.Rulesets.Objects.Legacy if (input.Length > 1 && int.TryParse(input.Substring(1), out int degree) && degree > 0) return PathType.BSpline(degree); - return new PathType(SplineType.BSpline); + return PathType.BEZIER; case 'L': return PathType.LINEAR; From 6d7d826b8b76f775bf59d600d493912a92950540 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 20 Nov 2023 15:08:58 +0900 Subject: [PATCH 46/91] Fix incorrect legacy conversion when B-splines are used --- osu.Game/Database/LegacyBeatmapExporter.cs | 3 ++- osu.Game/Rulesets/Objects/BezierConverter.cs | 8 ++++++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/osu.Game/Database/LegacyBeatmapExporter.cs b/osu.Game/Database/LegacyBeatmapExporter.cs index 9ca12a79dd..69120ea885 100644 --- a/osu.Game/Database/LegacyBeatmapExporter.cs +++ b/osu.Game/Database/LegacyBeatmapExporter.cs @@ -85,7 +85,8 @@ namespace osu.Game.Database if (hasPath.Path.ControlPoints.Count > 1) hasPath.Path.ControlPoints[^1].Type = null; - if (BezierConverter.CountSegments(hasPath.Path.ControlPoints) <= 1) continue; + if (BezierConverter.CountSegments(hasPath.Path.ControlPoints) <= 1 + && hasPath.Path.ControlPoints[0].Type!.Value.Degree == null) continue; var newControlPoints = BezierConverter.ConvertToModernBezier(hasPath.Path.ControlPoints); diff --git a/osu.Game/Rulesets/Objects/BezierConverter.cs b/osu.Game/Rulesets/Objects/BezierConverter.cs index 4a68161899..638975630e 100644 --- a/osu.Game/Rulesets/Objects/BezierConverter.cs +++ b/osu.Game/Rulesets/Objects/BezierConverter.cs @@ -164,9 +164,13 @@ namespace osu.Game.Rulesets.Objects break; case SplineType.BSpline: - for (int j = 0; j < segmentVertices.Length - 1; j++) + var bSplineResult = segmentType.Degree == null + ? segmentVertices + : PathApproximator.BSplineToBezier(segmentVertices, segmentType.Degree.Value); + + for (int j = 0; j < bSplineResult.Length - 1; j++) { - result.Add(new PathControlPoint(segmentVertices[j], j == 0 ? segmentType : null)); + result.Add(new PathControlPoint(bSplineResult[j], j == 0 ? PathType.BEZIER : null)); } break; From 46d4587c97fc8a6d7b3a2ea0e98366fe149f2ed3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 20 Nov 2023 15:34:01 +0900 Subject: [PATCH 47/91] Add test for slider drawing --- .../TestSceneSliderPlacementBlueprint.cs | 23 ++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderPlacementBlueprint.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderPlacementBlueprint.cs index ecfc8105f1..d1c94c9c9c 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderPlacementBlueprint.cs @@ -290,6 +290,27 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor assertControlPointType(0, PathType.LINEAR); } + [Test] + public void TestSliderDrawing() + { + addMovementStep(new Vector2(200)); + AddStep("press left button", () => InputManager.PressButton(MouseButton.Left)); + + addMovementStep(new Vector2(300, 200)); + addMovementStep(new Vector2(400, 200)); + addMovementStep(new Vector2(400, 300)); + addMovementStep(new Vector2(400)); + addMovementStep(new Vector2(300, 400)); + addMovementStep(new Vector2(200, 400)); + + AddStep("release left button", () => InputManager.ReleaseButton(MouseButton.Left)); + + assertPlaced(true); + assertLength(600, tolerance: 10); + assertControlPointCount(4); + assertControlPointType(0, PathType.BSpline(3)); + } + [Test] public void TestPlacePerfectCurveSegmentAlmostLinearlyExterior() { @@ -397,7 +418,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor private void assertPlaced(bool expected) => AddAssert($"slider {(expected ? "placed" : "not placed")}", () => (getSlider() != null) == expected); - private void assertLength(double expected) => AddAssert($"slider length is {expected}", () => Precision.AlmostEquals(expected, getSlider().Distance, 1)); + private void assertLength(double expected, double tolerance = 1) => AddAssert($"slider length is {expected}±{tolerance}", () => Precision.AlmostEquals(expected, getSlider().Distance, tolerance)); private void assertControlPointCount(int expected) => AddAssert($"has {expected} control points", () => getSlider().Path.ControlPoints.Count == expected); From 5f302662be30eff9eb12318c40651f1f776fb09c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 20 Nov 2023 15:34:23 +0900 Subject: [PATCH 48/91] Remove test terminally broken by introduction of slider drawing --- .../Editor/TestSceneSliderPlacementBlueprint.cs | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderPlacementBlueprint.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderPlacementBlueprint.cs index d1c94c9c9c..c7a21ba689 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderPlacementBlueprint.cs @@ -273,23 +273,6 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor assertControlPointType(2, PathType.PERFECT_CURVE); } - [Test] - public void TestBeginPlacementWithoutReleasingMouse() - { - addMovementStep(new Vector2(200)); - AddStep("press left button", () => InputManager.PressButton(MouseButton.Left)); - - addMovementStep(new Vector2(400, 200)); - AddStep("release left button", () => InputManager.ReleaseButton(MouseButton.Left)); - - addClickStep(MouseButton.Right); - - assertPlaced(true); - assertLength(200); - assertControlPointCount(2); - assertControlPointType(0, PathType.LINEAR); - } - [Test] public void TestSliderDrawing() { From 8e39dbbff1898830ab70c5b418ec31b661fede42 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 20 Nov 2023 15:41:26 +0900 Subject: [PATCH 49/91] Adjust casing of curve type menu items The "Perfect curve" one in particular... fixes test failures, as some tests were relying on this particular casing. But the new version feels more correct anyway, so it's whatever. --- osu.Game/Rulesets/Objects/Types/PathType.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Rulesets/Objects/Types/PathType.cs b/osu.Game/Rulesets/Objects/Types/PathType.cs index 37c1d0ab50..f84d43e3e7 100644 --- a/osu.Game/Rulesets/Objects/Types/PathType.cs +++ b/osu.Game/Rulesets/Objects/Types/PathType.cs @@ -56,13 +56,13 @@ namespace osu.Game.Rulesets.Objects.Types return "Catmull"; case SplineType.BSpline: - return Degree == null ? "Bezier" : "B-Spline"; + return Degree == null ? "Bezier" : "B-spline"; case SplineType.Linear: return "Linear"; case SplineType.PerfectCurve: - return "Perfect Curve"; + return "Perfect curve"; default: return Type.ToString(); From 43dbd65708de80e4e671e042e76496ffb7526d57 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 20 Nov 2023 15:53:25 +0900 Subject: [PATCH 50/91] Show Catmull as a control point type option if selection already contains it --- .../TestScenePathControlPointVisualiser.cs | 26 ++++++++++++++++++- .../Components/PathControlPointVisualiser.cs | 4 +-- 2 files changed, 26 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestScenePathControlPointVisualiser.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestScenePathControlPointVisualiser.cs index 811ecf53e9..8234381283 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestScenePathControlPointVisualiser.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestScenePathControlPointVisualiser.cs @@ -148,6 +148,30 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor assertControlPointPathType(3, null); } + [Test] + public void TestCatmullAvailableIffSelectionContainsCatmull() + { + createVisualiser(true); + + addControlPointStep(new Vector2(200), PathType.CATMULL); + addControlPointStep(new Vector2(300)); + addControlPointStep(new Vector2(500, 300)); + addControlPointStep(new Vector2(700, 200)); + addControlPointStep(new Vector2(500, 100)); + + moveMouseToControlPoint(2); + AddStep("select first and third control point", () => + { + visualiser.Pieces[0].IsSelected.Value = true; + visualiser.Pieces[2].IsSelected.Value = true; + }); + addContextMenuItemStep("Catmull"); + + assertControlPointPathType(0, PathType.CATMULL); + assertControlPointPathType(2, PathType.CATMULL); + assertControlPointPathType(4, null); + } + private void createVisualiser(bool allowSelection) => AddStep("create visualiser", () => Child = visualiser = new PathControlPointVisualiser(slider, allowSelection) { Anchor = Anchor.Centre, @@ -158,7 +182,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor private void addControlPointStep(Vector2 position, PathType? type) { - AddStep($"add {type} control point at {position}", () => + AddStep($"add {type?.Type} control point at {position}", () => { slider.Path.ControlPoints.Add(new PathControlPoint(position, type)); }); diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs index 95c72a0a62..751ca7e753 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs @@ -372,9 +372,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components curveTypeItems.Add(createMenuItemForPathType(PathType.BEZIER)); curveTypeItems.Add(createMenuItemForPathType(PathType.BSpline(3))); - var hoveredPiece = Pieces.FirstOrDefault(p => p.IsHovered); - - if (hoveredPiece?.ControlPoint.Type == PathType.CATMULL) + if (selectedPieces.Any(piece => piece.ControlPoint.Type?.Type == SplineType.Catmull)) curveTypeItems.Add(createMenuItemForPathType(PathType.CATMULL)); var menuItems = new List From 4061417ac8cff9242cf5582fdb8b173f7095059e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 20 Nov 2023 15:54:43 +0900 Subject: [PATCH 51/91] Decrease default value for slider tolerance Highly subjective change, but at 50 the drawing just did not feel responsive enough to input. --- osu.Game.Rulesets.Osu/Edit/OsuSliderDrawingSettingsProvider.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSliderDrawingSettingsProvider.cs b/osu.Game.Rulesets.Osu/Edit/OsuSliderDrawingSettingsProvider.cs index e44f0265c8..7e72eee510 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuSliderDrawingSettingsProvider.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuSliderDrawingSettingsProvider.cs @@ -19,7 +19,7 @@ namespace osu.Game.Rulesets.Osu.Edit Precision = 0.01f }; - private readonly BindableInt sliderTolerance = new BindableInt(50) + private readonly BindableInt sliderTolerance = new BindableInt(40) { MinValue = 5, MaxValue = 100 From ded9981d07b8bc54fd43a53a76f6335aab5d636f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 20 Nov 2023 17:55:01 +0900 Subject: [PATCH 52/91] Update framework --- osu.Android.props | 2 +- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index dd5a8996fb..8175869405 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -10,7 +10,7 @@ true - + diff --git a/osu.iOS.props b/osu.iOS.props index 9a0832b4e7..a5425ba4c7 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -23,6 +23,6 @@ iossimulator-x64 - + From 492fd06c624b2d8d8ff65464798bcde72c4fd51c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 20 Nov 2023 19:21:23 +0900 Subject: [PATCH 53/91] Remove unnecessary null override --- .../Blueprints/Sliders/Components/PathControlPointVisualiser.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs index 751ca7e753..3128f46357 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs @@ -405,7 +405,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components int totalCount = Pieces.Count(p => p.IsSelected.Value); int countOfState = Pieces.Where(p => p.IsSelected.Value).Count(p => p.ControlPoint.Type == type); - var item = new TernaryStateRadioMenuItem(type == null ? "Inherit" : type!.Value.Description, MenuItemType.Standard, _ => + var item = new TernaryStateRadioMenuItem(type == null ? "Inherit" : type.Value.Description, MenuItemType.Standard, _ => { foreach (var p in Pieces.Where(p => p.IsSelected.Value)) updatePathType(p, type); From c9e8d66e1970fa8498761b4fb5fe868bf879d1d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 20 Nov 2023 21:02:12 +0900 Subject: [PATCH 54/91] Improve xmldoc --- osu.Game/OsuGame.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 4e37481ba7..3fda18b7cb 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -555,9 +555,9 @@ namespace osu.Game public void ShowChangelogBuild(string updateStream, string version) => waitForReady(() => changelogOverlay, _ => changelogOverlay.ShowBuild(updateStream, version)); /// - /// Seek to a given timestamp in the Editor and select relevant HitObjects if needed + /// Seeks to the provided if the editor is currently open. + /// Can also select objects as indicated by the (depends on ruleset implementation). /// - /// The timestamp and the selected objects public void HandleTimestamp(string timestamp) { if (ScreenStack.CurrentScreen is not Editor editor) From 0e0ab66148ec99a0458b5102541e1a58206a2887 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 20 Nov 2023 21:27:24 +0900 Subject: [PATCH 55/91] Simplify parsing code Less methods, less smeared around logic, saner data types. --- osu.Game/Localisation/EditorStrings.cs | 5 --- .../Rulesets/Edit/EditorTimestampParser.cs | 41 ++++++++++++------- osu.Game/Screens/Edit/Editor.cs | 29 +++---------- 3 files changed, 32 insertions(+), 43 deletions(-) diff --git a/osu.Game/Localisation/EditorStrings.cs b/osu.Game/Localisation/EditorStrings.cs index 227dbc5e0c..e762455e8b 100644 --- a/osu.Game/Localisation/EditorStrings.cs +++ b/osu.Game/Localisation/EditorStrings.cs @@ -129,11 +129,6 @@ namespace osu.Game.Localisation /// public static LocalisableString FailedToProcessTimestamp => new TranslatableString(getKey(@"failed_to_process_timestamp"), @"Failed to process timestamp"); - /// - /// "The timestamp was too long to process" - /// - public static LocalisableString TooLongTimestamp => new TranslatableString(getKey(@"too_long_timestamp"), @"The timestamp was too long to process"); - private static string getKey(string key) => $@"{prefix}:{key}"; } } diff --git a/osu.Game/Rulesets/Edit/EditorTimestampParser.cs b/osu.Game/Rulesets/Edit/EditorTimestampParser.cs index e36822cc63..bdfdce432e 100644 --- a/osu.Game/Rulesets/Edit/EditorTimestampParser.cs +++ b/osu.Game/Rulesets/Edit/EditorTimestampParser.cs @@ -2,8 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Diagnostics; -using System.Linq; +using System.Diagnostics.CodeAnalysis; using System.Text.RegularExpressions; namespace osu.Game.Rulesets.Edit @@ -12,26 +11,40 @@ namespace osu.Game.Rulesets.Edit { // 00:00:000 (...) - test // original osu-web regex: https://github.com/ppy/osu-web/blob/3b1698639244cfdaf0b41c68bfd651ea729ec2e3/resources/js/utils/beatmapset-discussion-helper.ts#L78 - public static readonly Regex TIME_REGEX = new Regex(@"\b(((\d{2,}):([0-5]\d)[:.](\d{3}))(\s\([^)]+\))?)"); + public static readonly Regex TIME_REGEX = new Regex(@"\b(((?\d{2,}):(?[0-5]\d)[:.](?\d{3}))(?\s\([^)]+\))?)", RegexOptions.Compiled); - public static string[] GetRegexGroups(string timestamp) + public static bool TryParse(string timestamp, [NotNullWhen(true)] out TimeSpan? parsedTime, out string? parsedSelection) { Match match = TIME_REGEX.Match(timestamp); - string[] result = match.Success - ? match.Groups.Values.Where(x => x is not Match && !x.Value.Contains(':')).Select(x => x.Value).ToArray() - : Array.Empty(); + if (!match.Success) + { + parsedTime = null; + parsedSelection = null; + return false; + } - return result; - } + bool result = true; - public static double GetTotalMilliseconds(params string[] timesGroup) - { - int[] times = timesGroup.Select(int.Parse).ToArray(); + result &= int.TryParse(match.Groups[@"minutes"].Value, out int timeMin); + result &= int.TryParse(match.Groups[@"seconds"].Value, out int timeSec); + result &= int.TryParse(match.Groups[@"milliseconds"].Value, out int timeMsec); - Debug.Assert(times.Length == 3); + // somewhat sane limit for timestamp duration (10 hours). + result &= timeMin < 600; - return (times[0] * 60 + times[1]) * 1000 + times[2]; + if (!result) + { + parsedTime = null; + parsedSelection = null; + return false; + } + + parsedTime = TimeSpan.FromMinutes(timeMin) + TimeSpan.FromSeconds(timeSec) + TimeSpan.FromMilliseconds(timeMsec); + parsedSelection = match.Groups[@"selection"].Value.Trim(); + if (!string.IsNullOrEmpty(parsedSelection)) + parsedSelection = parsedSelection[1..^1]; + return true; } } } diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 03d3e3a1f8..c38e88cd02 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -1141,9 +1141,7 @@ namespace osu.Game.Screens.Edit public void HandleTimestamp(string timestamp) { - string[] groups = EditorTimestampParser.GetRegexGroups(timestamp); - - if (groups.Length != 4 || string.IsNullOrEmpty(groups[0])) + if (!EditorTimestampParser.TryParse(timestamp, out var timeSpan, out string selection)) { Schedule(() => notifications?.Post(new SimpleNotification { @@ -1153,31 +1151,14 @@ namespace osu.Game.Screens.Edit return; } - string timeMin = groups[0]; - string timeSec = groups[1]; - string timeMss = groups[2]; - string objectsGroup = groups[3].Replace("(", "").Replace(")", "").Trim(); - - // Currently, lazer chat highlights infinite-long editor links like `10000000000:00:000 (1)` - // Limit timestamp link length at 30000 min (50 hr) to avoid parsing issues - if (string.IsNullOrEmpty(timeMin) || timeMin.Length > 5 || double.Parse(timeMin) > 30_000) - { - Schedule(() => notifications?.Post(new SimpleNotification - { - Icon = FontAwesome.Solid.ExclamationTriangle, - Text = EditorStrings.TooLongTimestamp - })); - return; - } - editorBeatmap.SelectedHitObjects.Clear(); if (clock.IsRunning) clock.Stop(); - double position = EditorTimestampParser.GetTotalMilliseconds(timeMin, timeSec, timeMss); + double position = timeSpan.Value.TotalMilliseconds; - if (string.IsNullOrEmpty(objectsGroup)) + if (string.IsNullOrEmpty(selection)) { clock.SeekSmoothlyTo(position); return; @@ -1194,8 +1175,8 @@ namespace osu.Game.Screens.Edit if (Mode.Value != EditorScreenMode.Compose) Mode.Value = EditorScreenMode.Compose; - // Let the Ruleset handle selection - currentScreen.Dependencies.Get().SelectHitObjects(position, objectsGroup); + // Delegate handling the selection to the ruleset. + currentScreen.Dependencies.Get().SelectHitObjects(position, selection); } public double SnapTime(double time, double? referenceTime) => editorBeatmap.SnapTime(time, referenceTime); From 246aacb216d692b7bc54ae7fccebfd5869561d82 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 20 Nov 2023 21:29:19 +0900 Subject: [PATCH 56/91] Remove unnecessary guard Setting a bindable's value to something if that value is already there is a no-op (doesn't trigger bindings / callbacks). --- osu.Game/Screens/Edit/Editor.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index c38e88cd02..888f535cc6 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -1172,8 +1172,7 @@ namespace osu.Game.Screens.Edit clock.SeekSmoothlyTo(position); - if (Mode.Value != EditorScreenMode.Compose) - Mode.Value = EditorScreenMode.Compose; + Mode.Value = EditorScreenMode.Compose; // Delegate handling the selection to the ruleset. currentScreen.Dependencies.Get().SelectHitObjects(position, selection); From 234ef6f92379b3cbf0c9a07f2334f2a068f9fe95 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 20 Nov 2023 21:34:36 +0900 Subject: [PATCH 57/91] Rectify selection keep-alive logic --- .../Components/EditorBlueprintContainer.cs | 27 ++++++++++++------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/EditorBlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/EditorBlueprintContainer.cs index a311054ffc..378d378be3 100644 --- a/osu.Game/Screens/Edit/Compose/Components/EditorBlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/EditorBlueprintContainer.cs @@ -51,10 +51,7 @@ namespace osu.Game.Screens.Edit.Compose.Components Beatmap.HitObjectAdded += AddBlueprintFor; Beatmap.HitObjectRemoved += RemoveBlueprintFor; - - // This makes sure HitObjects will have active Blueprints ready to display - // after clicking on an Editor Timestamp/Link - Beatmap.SelectedHitObjects.CollectionChanged += selectionChanged; + Beatmap.SelectedHitObjects.CollectionChanged += updateSelectionLifetime; if (Composer != null) { @@ -149,13 +146,23 @@ namespace osu.Game.Screens.Edit.Compose.Components SelectedItems.AddRange(Beatmap.HitObjects.Except(SelectedItems).ToArray()); } - private void selectionChanged(object sender, NotifyCollectionChangedEventArgs e) + /// + /// Ensures that newly-selected hitobjects are kept alive + /// and drops that keep-alive from newly-deselected objects. + /// + private void updateSelectionLifetime(object sender, NotifyCollectionChangedEventArgs e) { - if (e == null || e.Action != NotifyCollectionChangedAction.Add || e.NewItems == null) - return; + if (e.NewItems != null) + { + foreach (HitObject newSelection in e.NewItems) + Composer.Playfield.SetKeepAlive(newSelection, true); + } - foreach (HitObject item in e.NewItems) - Composer.Playfield.SetKeepAlive(item, true); + if (e.OldItems != null) + { + foreach (HitObject oldSelection in e.OldItems) + Composer.Playfield.SetKeepAlive(oldSelection, false); + } } protected override void OnBlueprintSelected(SelectionBlueprint blueprint) @@ -180,7 +187,7 @@ namespace osu.Game.Screens.Edit.Compose.Components { Beatmap.HitObjectAdded -= AddBlueprintFor; Beatmap.HitObjectRemoved -= RemoveBlueprintFor; - Beatmap.SelectedHitObjects.CollectionChanged -= selectionChanged; + Beatmap.SelectedHitObjects.CollectionChanged -= updateSelectionLifetime; } usageEventBuffer?.Dispose(); From b6215b2809aac10f2bbf82403b76c6ca37f8d285 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 20 Nov 2023 21:37:29 +0900 Subject: [PATCH 58/91] Rename and document `SelectFromTimestamp` --- .../Edit/ManiaHitObjectComposer.cs | 2 +- osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs | 2 +- osu.Game/Rulesets/Edit/HitObjectComposer.cs | 13 +++++++++---- osu.Game/Screens/Edit/Editor.cs | 2 +- 4 files changed, 12 insertions(+), 7 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs b/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs index 92ecea812c..df6c62da51 100644 --- a/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs +++ b/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs @@ -55,7 +55,7 @@ namespace osu.Game.Rulesets.Mania.Edit public override string ConvertSelectionToString() => string.Join(',', EditorBeatmap.SelectedHitObjects.Cast().OrderBy(h => h.StartTime).Select(h => $"{h.StartTime}|{h.Column}")); - public override void SelectHitObjects(double timestamp, string objectDescription) + public override void SelectFromTimestamp(double timestamp, string objectDescription) { if (!selection_regex.IsMatch(objectDescription)) return; diff --git a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs index 2afbd83ce5..585d692978 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs @@ -110,7 +110,7 @@ namespace osu.Game.Rulesets.Osu.Edit public override string ConvertSelectionToString() => string.Join(',', selectedHitObjects.Cast().OrderBy(h => h.StartTime).Select(h => (h.IndexInCurrentCombo + 1).ToString())); - public override void SelectHitObjects(double timestamp, string objectDescription) + public override void SelectFromTimestamp(double timestamp, string objectDescription) { if (!selection_regex.IsMatch(objectDescription)) return; diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index 52a525e84f..50e6393895 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -526,14 +526,19 @@ namespace osu.Game.Rulesets.Edit /// public abstract bool CursorInPlacementArea { get; } + /// + /// Returns a string representing the current selection. + /// The inverse method to . + /// public virtual string ConvertSelectionToString() => string.Empty; /// - /// Each ruleset has it's own selection method + /// Selects objects based on the supplied and . + /// The inverse method to . /// - /// The given timestamp - /// The selected object information between the brackets - public virtual void SelectHitObjects(double timestamp, string objectDescription) { } + /// The time instant to seek to, in milliseconds. + /// The ruleset-specific description of objects to select at the given timestamp. + public virtual void SelectFromTimestamp(double timestamp, string objectDescription) { } #region IPositionSnapProvider diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 888f535cc6..fb34767315 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -1175,7 +1175,7 @@ namespace osu.Game.Screens.Edit Mode.Value = EditorScreenMode.Compose; // Delegate handling the selection to the ruleset. - currentScreen.Dependencies.Get().SelectHitObjects(position, selection); + currentScreen.Dependencies.Get().SelectFromTimestamp(position, selection); } public double SnapTime(double time, double? referenceTime) => editorBeatmap.SnapTime(time, referenceTime); From c16afeb34700b22a09932ad792ae801c0c5bffb9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 20 Nov 2023 21:57:07 +0900 Subject: [PATCH 59/91] Fix tests --- .../Editing/TestSceneOpenEditorTimestamp.cs | 38 +++++++++---------- 1 file changed, 17 insertions(+), 21 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneOpenEditorTimestamp.cs b/osu.Game.Tests/Visual/Editing/TestSceneOpenEditorTimestamp.cs index bc31924e2c..86ceb73f7a 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneOpenEditorTimestamp.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneOpenEditorTimestamp.cs @@ -83,46 +83,42 @@ namespace osu.Game.Tests.Visual.Editing RulesetInfo rulesetInfo = new OsuRuleset().RulesetInfo; addStepClickLink("00:00:000", waitForSeek: false); - AddAssert("recieved 'must be in edit'", () => - Game.Notifications.AllNotifications.Count(x => x.Text == EditorStrings.MustBeInEdit) == 1 - ); + AddAssert("received 'must be in edit'", + () => Game.Notifications.AllNotifications.Count(x => x.Text == EditorStrings.MustBeInEdit), + () => Is.EqualTo(1)); AddStep("enter song select", () => Game.ChildrenOfType().Single().OnSolo.Invoke()); - AddAssert("entered song select", () => Game.ScreenStack.CurrentScreen is PlaySongSelect); + AddUntilStep("entered song select", () => Game.ScreenStack.CurrentScreen is PlaySongSelect); addStepClickLink("00:00:000 (1)", waitForSeek: false); - AddAssert("recieved 'must be in edit'", () => - Game.Notifications.AllNotifications.Count(x => x.Text == EditorStrings.MustBeInEdit) == 2 - ); + AddAssert("received 'must be in edit'", + () => Game.Notifications.AllNotifications.Count(x => x.Text == EditorStrings.MustBeInEdit), + () => Is.EqualTo(2)); setUpEditor(rulesetInfo); - AddAssert("is editor Osu", () => editorBeatmap.BeatmapInfo.Ruleset.Equals(rulesetInfo)); + AddAssert("ruleset is osu!", () => editorBeatmap.BeatmapInfo.Ruleset.Equals(rulesetInfo)); addStepClickLink("00:000", "invalid link", waitForSeek: false); - AddAssert("recieved 'failed to process'", () => - Game.Notifications.AllNotifications.Count(x => x.Text == EditorStrings.FailedToProcessTimestamp) == 1 - ); + AddAssert("received 'failed to process'", + () => Game.Notifications.AllNotifications.Count(x => x.Text == EditorStrings.FailedToProcessTimestamp), + () => Is.EqualTo(1)); addStepClickLink("50000:00:000", "too long link", waitForSeek: false); - AddAssert("recieved 'too long'", () => - Game.Notifications.AllNotifications.Count(x => x.Text == EditorStrings.TooLongTimestamp) == 1 - ); + AddAssert("received 'failed to process'", + () => Game.Notifications.AllNotifications.Count(x => x.Text == EditorStrings.FailedToProcessTimestamp), + () => Is.EqualTo(2)); } [Test] public void TestHandleCurrentScreenChanges() { - const long long_link_value = 1_000 * 60 * 1_000; RulesetInfo rulesetInfo = new OsuRuleset().RulesetInfo; setUpEditor(rulesetInfo); - AddAssert("is editor Osu", () => editorBeatmap.BeatmapInfo.Ruleset.Equals(rulesetInfo)); + AddAssert("is osu! ruleset", () => editorBeatmap.BeatmapInfo.Ruleset.Equals(rulesetInfo)); - addStepClickLink("1000:00:000", "long link"); - AddAssert("moved to end of track", () => - editorClock.CurrentTime == long_link_value - || (editorClock.TrackLength < long_link_value && editorClock.CurrentTime == editorClock.TrackLength) - ); + addStepClickLink("100:00:000", "long link"); + AddUntilStep("moved to end of track", () => editorClock.CurrentTime, () => Is.EqualTo(editorClock.TrackLength)); addStepScreenModeTo(EditorScreenMode.SongSetup); addStepClickLink("00:00:000"); From 364a3f75e15001b785f185c41ba69e28edecf926 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 20 Nov 2023 22:03:25 +0900 Subject: [PATCH 60/91] Compile regexes --- osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs | 6 +++--- osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs b/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs index df6c62da51..e876ff1c81 100644 --- a/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs +++ b/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs @@ -49,12 +49,12 @@ namespace osu.Game.Rulesets.Mania.Edit new HoldNoteCompositionTool() }; - // 123|0,456|1,789|2 ... - private static readonly Regex selection_regex = new Regex(@"^\d+\|\d+(,\d+\|\d+)*$"); - public override string ConvertSelectionToString() => string.Join(',', EditorBeatmap.SelectedHitObjects.Cast().OrderBy(h => h.StartTime).Select(h => $"{h.StartTime}|{h.Column}")); + // 123|0,456|1,789|2 ... + private static readonly Regex selection_regex = new Regex(@"^\d+\|\d+(,\d+\|\d+)*$", RegexOptions.Compiled); + public override void SelectFromTimestamp(double timestamp, string objectDescription) { if (!selection_regex.IsMatch(objectDescription)) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs index 585d692978..c3fd36b933 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs @@ -104,12 +104,12 @@ namespace osu.Game.Rulesets.Osu.Edit protected override ComposeBlueprintContainer CreateBlueprintContainer() => new OsuBlueprintContainer(this); - // 1,2,3,4 ... - private static readonly Regex selection_regex = new Regex(@"^\d+(,\d+)*$"); - public override string ConvertSelectionToString() => string.Join(',', selectedHitObjects.Cast().OrderBy(h => h.StartTime).Select(h => (h.IndexInCurrentCombo + 1).ToString())); + // 1,2,3,4 ... + private static readonly Regex selection_regex = new Regex(@"^\d+(,\d+)*$", RegexOptions.Compiled); + public override void SelectFromTimestamp(double timestamp, string objectDescription) { if (!selection_regex.IsMatch(objectDescription)) From a8fc73695f30b3d22c260c8ea030f3034d1c21d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 20 Nov 2023 22:04:08 +0900 Subject: [PATCH 61/91] Rename variable --- osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs b/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs index e876ff1c81..967cdb0e54 100644 --- a/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs +++ b/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs @@ -61,11 +61,11 @@ namespace osu.Game.Rulesets.Mania.Edit return; List remainingHitObjects = EditorBeatmap.HitObjects.Cast().Where(h => h.StartTime >= timestamp).ToList(); - string[] splitDescription = objectDescription.Split(',').ToArray(); + string[] objectDescriptions = objectDescription.Split(',').ToArray(); - for (int i = 0; i < splitDescription.Length; i++) + for (int i = 0; i < objectDescriptions.Length; i++) { - string[] split = splitDescription[i].Split('|').ToArray(); + string[] split = objectDescriptions[i].Split('|').ToArray(); if (split.Length != 2) continue; @@ -79,7 +79,7 @@ namespace osu.Game.Rulesets.Mania.Edit EditorBeatmap.SelectedHitObjects.Add(current); - if (i < splitDescription.Length - 1) + if (i < objectDescriptions.Length - 1) remainingHitObjects = remainingHitObjects.Where(h => h != current && h.StartTime >= current.StartTime).ToList(); } } From 745a04a243b90a1b83da308ea3e8e6467c9faacc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 20 Nov 2023 22:12:15 +0900 Subject: [PATCH 62/91] More test cleanup --- .../TestSceneOpenEditorTimestampInOsu.cs | 42 +++++++++---------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOpenEditorTimestampInOsu.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOpenEditorTimestampInOsu.cs index 93573b5ad8..753400e10e 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOpenEditorTimestampInOsu.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOpenEditorTimestampInOsu.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using System.Collections.Generic; using System.Linq; using NUnit.Framework; @@ -25,16 +26,17 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor addStepClickLink("00:00:000", "reset", false); } - private bool checkSnapAndSelectCombo(double startTime, params int[] comboNumbers) - { - bool checkCombos = comboNumbers.Any() - ? hasCombosInOrder(EditorBeatmap.SelectedHitObjects, comboNumbers) - : !EditorBeatmap.SelectedHitObjects.Any(); + private void checkSelection(Func startTime, params int[] comboNumbers) + => AddUntilStep($"seeked & selected {(comboNumbers.Any() ? string.Join(",", comboNumbers) : "nothing")}", () => + { + bool checkCombos = comboNumbers.Any() + ? hasCombosInOrder(EditorBeatmap.SelectedHitObjects, comboNumbers) + : !EditorBeatmap.SelectedHitObjects.Any(); - return EditorClock.CurrentTime == startTime - && EditorBeatmap.SelectedHitObjects.Count == comboNumbers.Length - && checkCombos; - } + return EditorClock.CurrentTime == startTime() + && EditorBeatmap.SelectedHitObjects.Count == comboNumbers.Length + && checkCombos; + }); private bool hasCombosInOrder(IEnumerable selected, params int[] comboNumbers) { @@ -51,19 +53,19 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor public void TestNormalSelection() { addStepClickLink("00:02:170 (1,2,3)"); - AddAssert("snap and select 1-2-3", () => checkSnapAndSelectCombo(2_170, 1, 2, 3)); + checkSelection(() => 2_170, 1, 2, 3); addReset(); addStepClickLink("00:04:748 (2,3,4,1,2)"); - AddAssert("snap and select 2-3-4-1-2", () => checkSnapAndSelectCombo(4_748, 2, 3, 4, 1, 2)); + checkSelection(() => 4_748, 2, 3, 4, 1, 2); addReset(); addStepClickLink("00:02:170 (1,1,1)"); - AddAssert("snap and select 1-1-1", () => checkSnapAndSelectCombo(2_170, 1, 1, 1)); + checkSelection(() => 2_170, 1, 1, 1); addReset(); addStepClickLink("00:02:873 (2,2,2,2)"); - AddAssert("snap and select 2-2-2-2", () => checkSnapAndSelectCombo(2_873, 2, 2, 2, 2)); + checkSelection(() => 2_873, 2, 2, 2, 2); } [Test] @@ -71,24 +73,22 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor { HitObject firstObject = null!; + AddStep("retrieve first object", () => firstObject = EditorBeatmap.HitObjects.First()); + addStepClickLink("00:00:000 (0)", "invalid combo"); - AddAssert("snap to next, select none", () => - { - firstObject = EditorBeatmap.HitObjects.First(); - return checkSnapAndSelectCombo(firstObject.StartTime); - }); + checkSelection(() => firstObject.StartTime); addReset(); addStepClickLink("00:00:000 (1)", "wrong offset"); - AddAssert("snap and select 1", () => checkSnapAndSelectCombo(firstObject.StartTime, 1)); + checkSelection(() => firstObject.StartTime, 1); addReset(); addStepClickLink("00:00:956 (2,3,4)", "wrong offset"); - AddAssert("snap to next, select 2-3-4", () => checkSnapAndSelectCombo(firstObject.StartTime, 2, 3, 4)); + checkSelection(() => firstObject.StartTime, 2, 3, 4); addReset(); addStepClickLink("00:00:956 (956|1,956|2)", "mania link"); - AddAssert("snap to next, select none", () => checkSnapAndSelectCombo(firstObject.StartTime)); + checkSelection(() => firstObject.StartTime); } } } From 750bbc8a19cfdb0f1c3133e9bd86e02bacb51f0d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 21 Nov 2023 00:17:25 +0900 Subject: [PATCH 63/91] Simplify null checks --- .../Sliders/Components/PathControlPointVisualiser.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs index 3128f46357..ef8a033014 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs @@ -242,7 +242,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components { int indexInSegment = piece.PointsInSegment.IndexOf(piece.ControlPoint); - if (type.HasValue && type.Value.Type == SplineType.PerfectCurve) + if (type?.Type == SplineType.PerfectCurve) { // Can't always create a circular arc out of 4 or more points, // so we split the segment into one 3-point circular arc segment @@ -405,7 +405,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components int totalCount = Pieces.Count(p => p.IsSelected.Value); int countOfState = Pieces.Where(p => p.IsSelected.Value).Count(p => p.ControlPoint.Type == type); - var item = new TernaryStateRadioMenuItem(type == null ? "Inherit" : type.Value.Description, MenuItemType.Standard, _ => + var item = new TernaryStateRadioMenuItem(type?.Description ?? "Inherit", MenuItemType.Standard, _ => { foreach (var p in Pieces.Where(p => p.IsSelected.Value)) updatePathType(p, type); From 6f5c468a83cd0341616d01d10ef5345568a7d943 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 21 Nov 2023 00:21:44 +0900 Subject: [PATCH 64/91] Rename settings class --- .../Blueprints/Sliders/SliderPlacementBlueprint.cs | 8 ++++---- ...ovider.cs => FreehandSliderSettingsProvider.cs} | 14 +++++++------- osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs | 6 +++--- 3 files changed, 14 insertions(+), 14 deletions(-) rename osu.Game.Rulesets.Osu/Edit/{OsuSliderDrawingSettingsProvider.cs => FreehandSliderSettingsProvider.cs} (98%) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs index fb2c1d5149..df8417d8ff 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs @@ -46,7 +46,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders private IDistanceSnapProvider distanceSnapProvider { get; set; } [Resolved(CanBeNull = true)] - private OsuSliderDrawingSettingsProvider drawingSettingsProvider { get; set; } + private FreehandSliderSettingsProvider freehandSettingsProvider { get; set; } private readonly IncrementalBSplineBuilder bSplineBuilder = new IncrementalBSplineBuilder(); @@ -80,16 +80,16 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders base.LoadComplete(); inputManager = GetContainingInputManager(); - if (drawingSettingsProvider != null) + if (freehandSettingsProvider != null) { - drawingSettingsProvider.Tolerance.BindValueChanged(e => + freehandSettingsProvider.Tolerance.BindValueChanged(e => { if (bSplineBuilder.Tolerance != e.NewValue) bSplineBuilder.Tolerance = e.NewValue; updateSliderPathFromBSplineBuilder(); }); - drawingSettingsProvider.CornerThreshold.BindValueChanged(e => + freehandSettingsProvider.CornerThreshold.BindValueChanged(e => { if (bSplineBuilder.CornerThreshold != e.NewValue) bSplineBuilder.CornerThreshold = e.NewValue; diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSliderDrawingSettingsProvider.cs b/osu.Game.Rulesets.Osu/Edit/FreehandSliderSettingsProvider.cs similarity index 98% rename from osu.Game.Rulesets.Osu/Edit/OsuSliderDrawingSettingsProvider.cs rename to osu.Game.Rulesets.Osu/Edit/FreehandSliderSettingsProvider.cs index 7e72eee510..9ad2b5d0f5 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuSliderDrawingSettingsProvider.cs +++ b/osu.Game.Rulesets.Osu/Edit/FreehandSliderSettingsProvider.cs @@ -10,7 +10,7 @@ using osu.Game.Rulesets.Edit; namespace osu.Game.Rulesets.Osu.Edit { - public partial class OsuSliderDrawingSettingsProvider : Drawable + public partial class FreehandSliderSettingsProvider : Drawable { public BindableFloat Tolerance { get; } = new BindableFloat(1.5f) { @@ -19,12 +19,6 @@ namespace osu.Game.Rulesets.Osu.Edit Precision = 0.01f }; - private readonly BindableInt sliderTolerance = new BindableInt(40) - { - MinValue = 5, - MaxValue = 100 - }; - public BindableFloat CornerThreshold { get; } = new BindableFloat(0.4f) { MinValue = 0.05f, @@ -32,6 +26,12 @@ namespace osu.Game.Rulesets.Osu.Edit Precision = 0.01f }; + private readonly BindableInt sliderTolerance = new BindableInt(40) + { + MinValue = 5, + MaxValue = 100 + }; + private readonly BindableInt sliderCornerThreshold = new BindableInt(40) { MinValue = 5, diff --git a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs index 0dc0d6fd31..7bf1a12149 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs @@ -64,7 +64,7 @@ namespace osu.Game.Rulesets.Osu.Edit protected readonly OsuDistanceSnapProvider DistanceSnapProvider = new OsuDistanceSnapProvider(); [Cached] - protected readonly OsuSliderDrawingSettingsProvider SliderDrawingSettingsProvider = new OsuSliderDrawingSettingsProvider(); + protected readonly FreehandSliderSettingsProvider FreehandSliderSettingsProvider = new FreehandSliderSettingsProvider(); [BackgroundDependencyLoader] private void load() @@ -102,8 +102,8 @@ namespace osu.Game.Rulesets.Osu.Edit RotationHandler = BlueprintContainer.SelectionHandler.RotationHandler, }); - AddInternal(SliderDrawingSettingsProvider); - SliderDrawingSettingsProvider.AttachToToolbox(RightToolbox); + AddInternal(FreehandSliderSettingsProvider); + FreehandSliderSettingsProvider.AttachToToolbox(RightToolbox); } protected override ComposeBlueprintContainer CreateBlueprintContainer() From 638c8f1adc2e135aff574e135c515adfbf384423 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 21 Nov 2023 00:25:23 +0900 Subject: [PATCH 65/91] Get rid of weird cruft and non-standard flow --- .../Edit/FreehandSliderSettingsProvider.cs | 66 ++++++++++--------- .../Edit/OsuHitObjectComposer.cs | 5 +- 2 files changed, 37 insertions(+), 34 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/FreehandSliderSettingsProvider.cs b/osu.Game.Rulesets.Osu/Edit/FreehandSliderSettingsProvider.cs index 9ad2b5d0f5..f4ee130938 100644 --- a/osu.Game.Rulesets.Osu/Edit/FreehandSliderSettingsProvider.cs +++ b/osu.Game.Rulesets.Osu/Edit/FreehandSliderSettingsProvider.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Utils; @@ -10,8 +11,13 @@ using osu.Game.Rulesets.Edit; namespace osu.Game.Rulesets.Osu.Edit { - public partial class FreehandSliderSettingsProvider : Drawable + public partial class FreehandSliderSettingsProvider : EditorToolboxGroup { + public FreehandSliderSettingsProvider() + : base("slider") + { + } + public BindableFloat Tolerance { get; } = new BindableFloat(1.5f) { MinValue = 0.05f, @@ -41,6 +47,34 @@ namespace osu.Game.Rulesets.Osu.Edit private ExpandableSlider toleranceSlider = null!; private ExpandableSlider cornerThresholdSlider = null!; + [BackgroundDependencyLoader] + private void load() + { + Children = new Drawable[] + { + toleranceSlider = new ExpandableSlider + { + Current = sliderTolerance + }, + cornerThresholdSlider = new ExpandableSlider + { + Current = sliderCornerThreshold + } + }; + + sliderTolerance.BindValueChanged(e => + { + toleranceSlider.ContractedLabelText = $"C. P. S.: {e.NewValue:N0}"; + toleranceSlider.ExpandedLabelText = $"Control Point Spacing: {e.NewValue:N0}"; + }, true); + + sliderCornerThreshold.BindValueChanged(e => + { + cornerThresholdSlider.ContractedLabelText = $"C. T.: {e.NewValue:N0}"; + cornerThresholdSlider.ExpandedLabelText = $"Corner Threshold: {e.NewValue:N0}"; + }, true); + } + protected override void LoadComplete() { base.LoadComplete(); @@ -70,35 +104,5 @@ namespace osu.Game.Rulesets.Osu.Edit sliderCornerThreshold.Value = newValue; }); } - - public void AttachToToolbox(ExpandingToolboxContainer toolboxContainer) - { - toolboxContainer.Add(new EditorToolboxGroup("slider") - { - Children = new Drawable[] - { - toleranceSlider = new ExpandableSlider - { - Current = sliderTolerance - }, - cornerThresholdSlider = new ExpandableSlider - { - Current = sliderCornerThreshold - } - } - }); - - sliderTolerance.BindValueChanged(e => - { - toleranceSlider.ContractedLabelText = $"C. P. S.: {e.NewValue:N0}"; - toleranceSlider.ExpandedLabelText = $"Control Point Spacing: {e.NewValue:N0}"; - }, true); - - sliderCornerThreshold.BindValueChanged(e => - { - cornerThresholdSlider.ContractedLabelText = $"C. T.: {e.NewValue:N0}"; - cornerThresholdSlider.ExpandedLabelText = $"Corner Threshold: {e.NewValue:N0}"; - }, true); - } } } diff --git a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs index 7bf1a12149..06584ef17a 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs @@ -64,7 +64,7 @@ namespace osu.Game.Rulesets.Osu.Edit protected readonly OsuDistanceSnapProvider DistanceSnapProvider = new OsuDistanceSnapProvider(); [Cached] - protected readonly FreehandSliderSettingsProvider FreehandSliderSettingsProvider = new FreehandSliderSettingsProvider(); + protected readonly FreehandSliderSettingsProvider FreehandlSliderToolboxGroup = new FreehandSliderSettingsProvider(); [BackgroundDependencyLoader] private void load() @@ -102,8 +102,7 @@ namespace osu.Game.Rulesets.Osu.Edit RotationHandler = BlueprintContainer.SelectionHandler.RotationHandler, }); - AddInternal(FreehandSliderSettingsProvider); - FreehandSliderSettingsProvider.AttachToToolbox(RightToolbox); + RightToolbox.Add(FreehandlSliderToolboxGroup); } protected override ComposeBlueprintContainer CreateBlueprintContainer() From e9f371a5811b72763a50fc1e5fffba5ef95e081f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 21 Nov 2023 09:59:49 +0900 Subject: [PATCH 66/91] Refactor slider settings class --- .../Sliders/SliderPlacementBlueprint.cs | 8 +- ...vider.cs => FreehandSliderToolboxGroup.cs} | 74 +++++++++---------- .../Edit/OsuHitObjectComposer.cs | 2 +- 3 files changed, 38 insertions(+), 46 deletions(-) rename osu.Game.Rulesets.Osu/Edit/{FreehandSliderSettingsProvider.cs => FreehandSliderToolboxGroup.cs} (53%) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs index df8417d8ff..f8b7642643 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs @@ -46,7 +46,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders private IDistanceSnapProvider distanceSnapProvider { get; set; } [Resolved(CanBeNull = true)] - private FreehandSliderSettingsProvider freehandSettingsProvider { get; set; } + private FreehandSliderToolboxGroup freehandToolboxGroup { get; set; } private readonly IncrementalBSplineBuilder bSplineBuilder = new IncrementalBSplineBuilder(); @@ -80,16 +80,16 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders base.LoadComplete(); inputManager = GetContainingInputManager(); - if (freehandSettingsProvider != null) + if (freehandToolboxGroup != null) { - freehandSettingsProvider.Tolerance.BindValueChanged(e => + freehandToolboxGroup.Tolerance.BindValueChanged(e => { if (bSplineBuilder.Tolerance != e.NewValue) bSplineBuilder.Tolerance = e.NewValue; updateSliderPathFromBSplineBuilder(); }); - freehandSettingsProvider.CornerThreshold.BindValueChanged(e => + freehandToolboxGroup.CornerThreshold.BindValueChanged(e => { if (bSplineBuilder.CornerThreshold != e.NewValue) bSplineBuilder.CornerThreshold = e.NewValue; diff --git a/osu.Game.Rulesets.Osu/Edit/FreehandSliderSettingsProvider.cs b/osu.Game.Rulesets.Osu/Edit/FreehandSliderToolboxGroup.cs similarity index 53% rename from osu.Game.Rulesets.Osu/Edit/FreehandSliderSettingsProvider.cs rename to osu.Game.Rulesets.Osu/Edit/FreehandSliderToolboxGroup.cs index f4ee130938..1974415d30 100644 --- a/osu.Game.Rulesets.Osu/Edit/FreehandSliderSettingsProvider.cs +++ b/osu.Game.Rulesets.Osu/Edit/FreehandSliderToolboxGroup.cs @@ -5,15 +5,14 @@ using System; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; -using osu.Framework.Utils; using osu.Game.Graphics.UserInterface; using osu.Game.Rulesets.Edit; namespace osu.Game.Rulesets.Osu.Edit { - public partial class FreehandSliderSettingsProvider : EditorToolboxGroup + public partial class FreehandSliderToolboxGroup : EditorToolboxGroup { - public FreehandSliderSettingsProvider() + public FreehandSliderToolboxGroup() : base("slider") { } @@ -32,13 +31,14 @@ namespace osu.Game.Rulesets.Osu.Edit Precision = 0.01f }; - private readonly BindableInt sliderTolerance = new BindableInt(40) + // We map internal ranges to a more standard range of values for display to the user. + private readonly BindableInt displayTolerance = new BindableInt(40) { MinValue = 5, MaxValue = 100 }; - private readonly BindableInt sliderCornerThreshold = new BindableInt(40) + private readonly BindableInt displayCornerThreshold = new BindableInt(40) { MinValue = 5, MaxValue = 100 @@ -54,55 +54,47 @@ namespace osu.Game.Rulesets.Osu.Edit { toleranceSlider = new ExpandableSlider { - Current = sliderTolerance + Current = displayTolerance }, cornerThresholdSlider = new ExpandableSlider { - Current = sliderCornerThreshold + Current = displayCornerThreshold } }; - - sliderTolerance.BindValueChanged(e => - { - toleranceSlider.ContractedLabelText = $"C. P. S.: {e.NewValue:N0}"; - toleranceSlider.ExpandedLabelText = $"Control Point Spacing: {e.NewValue:N0}"; - }, true); - - sliderCornerThreshold.BindValueChanged(e => - { - cornerThresholdSlider.ContractedLabelText = $"C. T.: {e.NewValue:N0}"; - cornerThresholdSlider.ExpandedLabelText = $"Corner Threshold: {e.NewValue:N0}"; - }, true); } protected override void LoadComplete() { base.LoadComplete(); - sliderTolerance.BindValueChanged(v => + displayTolerance.BindValueChanged(tolerance => { - float newValue = v.NewValue / 33f; - if (!Precision.AlmostEquals(newValue, Tolerance.Value)) - Tolerance.Value = newValue; - }); - Tolerance.BindValueChanged(v => + toleranceSlider.ContractedLabelText = $"C. P. S.: {tolerance.NewValue:N0}"; + toleranceSlider.ExpandedLabelText = $"Control Point Spacing: {tolerance.NewValue:N0}"; + + Tolerance.Value = displayToInternalTolerance(tolerance.NewValue); + }, true); + + displayCornerThreshold.BindValueChanged(threshold => { - int newValue = (int)Math.Round(v.NewValue * 33f); - if (sliderTolerance.Value != newValue) - sliderTolerance.Value = newValue; - }); - sliderCornerThreshold.BindValueChanged(v => - { - float newValue = v.NewValue / 100f; - if (!Precision.AlmostEquals(newValue, CornerThreshold.Value)) - CornerThreshold.Value = newValue; - }); - CornerThreshold.BindValueChanged(v => - { - int newValue = (int)Math.Round(v.NewValue * 100f); - if (sliderCornerThreshold.Value != newValue) - sliderCornerThreshold.Value = newValue; - }); + cornerThresholdSlider.ContractedLabelText = $"C. T.: {threshold.NewValue:N0}"; + cornerThresholdSlider.ExpandedLabelText = $"Corner Threshold: {threshold.NewValue:N0}"; + + CornerThreshold.Value = displayToInternalCornerThreshold(threshold.NewValue); + }, true); + + Tolerance.BindValueChanged(tolerance => + displayTolerance.Value = internalToDisplayTolerance(tolerance.NewValue) + ); + CornerThreshold.BindValueChanged(threshold => + displayCornerThreshold.Value = internalToDisplayCornerThreshold(threshold.NewValue) + ); + + float displayToInternalTolerance(float v) => v / 33f; + int internalToDisplayTolerance(float v) => (int)Math.Round(v * 33f); + + float displayToInternalCornerThreshold(float v) => v / 100f; + int internalToDisplayCornerThreshold(float v) => (int)Math.Round(v * 100f); } } } diff --git a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs index 06584ef17a..061f72d72f 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs @@ -64,7 +64,7 @@ namespace osu.Game.Rulesets.Osu.Edit protected readonly OsuDistanceSnapProvider DistanceSnapProvider = new OsuDistanceSnapProvider(); [Cached] - protected readonly FreehandSliderSettingsProvider FreehandlSliderToolboxGroup = new FreehandSliderSettingsProvider(); + protected readonly FreehandSliderToolboxGroup FreehandlSliderToolboxGroup = new FreehandSliderToolboxGroup(); [BackgroundDependencyLoader] private void load() From 3680024e3102d5dcca617371105d4c3bf3787945 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 21 Nov 2023 11:15:00 +0900 Subject: [PATCH 67/91] Fix tolerance not being transferred to blueprint in all cases --- .../Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs index f8b7642643..5f072eb171 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs @@ -87,7 +87,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders if (bSplineBuilder.Tolerance != e.NewValue) bSplineBuilder.Tolerance = e.NewValue; updateSliderPathFromBSplineBuilder(); - }); + }, true); freehandToolboxGroup.CornerThreshold.BindValueChanged(e => { From 405ab499e9dfd7c75477513aa38b344ffb901781 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 21 Nov 2023 14:24:10 +0900 Subject: [PATCH 68/91] Allow context menus to have visible spacers --- .../Visual/Editing/TestSceneEditorMenuBar.cs | 22 +++++++------- osu.Game/Graphics/UserInterface/OsuMenu.cs | 30 ++++++++++++++++++- .../UserInterface/OsuMenuItemSpacer.cs | 13 ++++++++ osu.Game/Overlays/SkinEditor/SkinEditor.cs | 10 +++---- .../SkinEditor/SkinSelectionHandler.cs | 7 ++--- .../Edit/Components/Menus/EditorMenuBar.cs | 27 +---------------- .../Components/Menus/EditorMenuItemSpacer.cs | 13 -------- osu.Game/Screens/Edit/Editor.cs | 10 +++---- 8 files changed, 67 insertions(+), 65 deletions(-) create mode 100644 osu.Game/Graphics/UserInterface/OsuMenuItemSpacer.cs delete mode 100644 osu.Game/Screens/Edit/Components/Menus/EditorMenuItemSpacer.cs diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorMenuBar.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorMenuBar.cs index dbcf66f005..fe47f5885d 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorMenuBar.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorMenuBar.cs @@ -34,51 +34,51 @@ namespace osu.Game.Tests.Visual.Editing { new MenuItem("File") { - Items = new[] + Items = new OsuMenuItem[] { new EditorMenuItem("Clear All Notes"), new EditorMenuItem("Open Difficulty..."), new EditorMenuItem("Save"), new EditorMenuItem("Create a new Difficulty..."), - new EditorMenuItemSpacer(), + new OsuMenuItemSpacer(), new EditorMenuItem("Revert to Saved"), new EditorMenuItem("Revert to Saved (Full)"), - new EditorMenuItemSpacer(), + new OsuMenuItemSpacer(), new EditorMenuItem("Test Beatmap"), new EditorMenuItem("Open AiMod"), - new EditorMenuItemSpacer(), + new OsuMenuItemSpacer(), new EditorMenuItem("Upload Beatmap..."), new EditorMenuItem("Export Package"), new EditorMenuItem("Export Map Package"), new EditorMenuItem("Import from..."), - new EditorMenuItemSpacer(), + new OsuMenuItemSpacer(), new EditorMenuItem("Open Song Folder"), new EditorMenuItem("Open .osu in Notepad"), new EditorMenuItem("Open .osb in Notepad"), - new EditorMenuItemSpacer(), + new OsuMenuItemSpacer(), new EditorMenuItem("Exit"), } }, new MenuItem("Timing") { - Items = new[] + Items = new OsuMenuItem[] { new EditorMenuItem("Time Signature"), new EditorMenuItem("Metronome Clicks"), - new EditorMenuItemSpacer(), + new OsuMenuItemSpacer(), new EditorMenuItem("Add Timing Section"), new EditorMenuItem("Add Inheriting Section"), new EditorMenuItem("Reset Current Section"), new EditorMenuItem("Delete Timing Section"), new EditorMenuItem("Resnap Current Section"), - new EditorMenuItemSpacer(), + new OsuMenuItemSpacer(), new EditorMenuItem("Timing Setup"), - new EditorMenuItemSpacer(), + new OsuMenuItemSpacer(), new EditorMenuItem("Resnap All Notes", MenuItemType.Destructive), new EditorMenuItem("Move all notes in time...", MenuItemType.Destructive), new EditorMenuItem("Recalculate Slider Lengths", MenuItemType.Destructive), new EditorMenuItem("Delete All Timing Sections", MenuItemType.Destructive), - new EditorMenuItemSpacer(), + new OsuMenuItemSpacer(), new EditorMenuItem("Set Current Position as Preview Point"), } }, diff --git a/osu.Game/Graphics/UserInterface/OsuMenu.cs b/osu.Game/Graphics/UserInterface/OsuMenu.cs index 73d57af793..e2aac297e3 100644 --- a/osu.Game/Graphics/UserInterface/OsuMenu.cs +++ b/osu.Game/Graphics/UserInterface/OsuMenu.cs @@ -6,13 +6,15 @@ using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Audio.Sample; -using osuTK.Graphics; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.UserInterface; +using osu.Framework.Input.Events; using osu.Game.Graphics.Containers; using osuTK; +using osuTK.Graphics; namespace osu.Game.Graphics.UserInterface { @@ -78,6 +80,9 @@ namespace osu.Game.Graphics.UserInterface { case StatefulMenuItem stateful: return new DrawableStatefulMenuItem(stateful); + + case OsuMenuItemSpacer spacer: + return new DrawableSpacer(spacer); } return new DrawableOsuMenuItem(item); @@ -89,5 +94,28 @@ namespace osu.Game.Graphics.UserInterface { Anchor = Direction == Direction.Horizontal ? Anchor.BottomLeft : Anchor.TopRight }; + + protected partial class DrawableSpacer : DrawableOsuMenuItem + { + public DrawableSpacer(MenuItem item) + : base(item) + { + Scale = new Vector2(1, 0.6f); + + AddInternal(new Box + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Colour = BackgroundColourHover, + RelativeSizeAxes = Axes.X, + Height = 2f, + Width = 0.8f, + }); + } + + protected override bool OnHover(HoverEvent e) => true; + + protected override bool OnClick(ClickEvent e) => true; + } } } diff --git a/osu.Game/Graphics/UserInterface/OsuMenuItemSpacer.cs b/osu.Game/Graphics/UserInterface/OsuMenuItemSpacer.cs new file mode 100644 index 0000000000..8a3a928c60 --- /dev/null +++ b/osu.Game/Graphics/UserInterface/OsuMenuItemSpacer.cs @@ -0,0 +1,13 @@ +// 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.Graphics.UserInterface +{ + public class OsuMenuItemSpacer : OsuMenuItem + { + public OsuMenuItemSpacer() + : base(" ") + { + } + } +} diff --git a/osu.Game/Overlays/SkinEditor/SkinEditor.cs b/osu.Game/Overlays/SkinEditor/SkinEditor.cs index 38eed55241..a816031668 100644 --- a/osu.Game/Overlays/SkinEditor/SkinEditor.cs +++ b/osu.Game/Overlays/SkinEditor/SkinEditor.cs @@ -151,23 +151,23 @@ namespace osu.Game.Overlays.SkinEditor { new MenuItem(CommonStrings.MenuBarFile) { - Items = new[] + Items = new OsuMenuItem[] { new EditorMenuItem(Web.CommonStrings.ButtonsSave, MenuItemType.Standard, () => Save()), new EditorMenuItem(CommonStrings.Export, MenuItemType.Standard, () => skins.ExportCurrentSkin()) { Action = { Disabled = !RuntimeInfo.IsDesktop } }, - new EditorMenuItemSpacer(), + new OsuMenuItemSpacer(), new EditorMenuItem(CommonStrings.RevertToDefault, MenuItemType.Destructive, () => dialogOverlay?.Push(new RevertConfirmDialog(revert))), - new EditorMenuItemSpacer(), + new OsuMenuItemSpacer(), new EditorMenuItem(CommonStrings.Exit, MenuItemType.Standard, () => skinEditorOverlay?.Hide()), }, }, new MenuItem(CommonStrings.MenuBarEdit) { - Items = new[] + Items = new OsuMenuItem[] { undoMenuItem = new EditorMenuItem(CommonStrings.Undo, MenuItemType.Standard, Undo), redoMenuItem = new EditorMenuItem(CommonStrings.Redo, MenuItemType.Standard, Redo), - new EditorMenuItemSpacer(), + new OsuMenuItemSpacer(), cutMenuItem = new EditorMenuItem(CommonStrings.Cut, MenuItemType.Standard, Cut), copyMenuItem = new EditorMenuItem(CommonStrings.Copy, MenuItemType.Standard, Copy), pasteMenuItem = new EditorMenuItem(CommonStrings.Paste, MenuItemType.Standard, Paste), diff --git a/osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs b/osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs index 52c012a15a..cf6fb60636 100644 --- a/osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs +++ b/osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs @@ -14,7 +14,6 @@ using osu.Framework.Utils; using osu.Game.Extensions; using osu.Game.Graphics.UserInterface; using osu.Game.Rulesets.Edit; -using osu.Game.Screens.Edit.Components.Menus; using osu.Game.Screens.Edit.Compose.Components; using osu.Game.Skinning; using osu.Game.Utils; @@ -249,7 +248,7 @@ namespace osu.Game.Overlays.SkinEditor Items = createAnchorItems((d, o) => ((Drawable)d).Origin == o, applyOrigins).ToArray() }; - yield return new EditorMenuItemSpacer(); + yield return new OsuMenuItemSpacer(); yield return new OsuMenuItem("Reset position", MenuItemType.Standard, () => { @@ -277,13 +276,13 @@ namespace osu.Game.Overlays.SkinEditor } }); - yield return new EditorMenuItemSpacer(); + yield return new OsuMenuItemSpacer(); yield return new OsuMenuItem("Bring to front", MenuItemType.Standard, () => skinEditor.BringSelectionToFront()); yield return new OsuMenuItem("Send to back", MenuItemType.Standard, () => skinEditor.SendSelectionToBack()); - yield return new EditorMenuItemSpacer(); + yield return new OsuMenuItemSpacer(); foreach (var item in base.GetContextMenuItemsForSelection(selection)) yield return item; diff --git a/osu.Game/Screens/Edit/Components/Menus/EditorMenuBar.cs b/osu.Game/Screens/Edit/Components/Menus/EditorMenuBar.cs index fb0ae2df73..a67375b0a4 100644 --- a/osu.Game/Screens/Edit/Components/Menus/EditorMenuBar.cs +++ b/osu.Game/Screens/Edit/Components/Menus/EditorMenuBar.cs @@ -4,11 +4,9 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; using osu.Framework.Graphics.UserInterface; -using osu.Framework.Input.Events; using osu.Game.Graphics; using osu.Game.Graphics.UserInterface; using osu.Game.Overlays; @@ -151,7 +149,7 @@ namespace osu.Game.Screens.Edit.Components.Menus { switch (item) { - case EditorMenuItemSpacer spacer: + case OsuMenuItemSpacer spacer: return new DrawableSpacer(spacer); case StatefulMenuItem stateful: @@ -195,29 +193,6 @@ namespace osu.Game.Screens.Edit.Components.Menus Foreground.Padding = new MarginPadding { Vertical = 2 }; } } - - private partial class DrawableSpacer : DrawableOsuMenuItem - { - public DrawableSpacer(MenuItem item) - : base(item) - { - Scale = new Vector2(1, 0.6f); - - AddInternal(new Box - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Colour = BackgroundColourHover, - RelativeSizeAxes = Axes.X, - Height = 2f, - Width = 0.8f, - }); - } - - protected override bool OnHover(HoverEvent e) => true; - - protected override bool OnClick(ClickEvent e) => true; - } } } } diff --git a/osu.Game/Screens/Edit/Components/Menus/EditorMenuItemSpacer.cs b/osu.Game/Screens/Edit/Components/Menus/EditorMenuItemSpacer.cs deleted file mode 100644 index 4e75a92e19..0000000000 --- a/osu.Game/Screens/Edit/Components/Menus/EditorMenuItemSpacer.cs +++ /dev/null @@ -1,13 +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.Screens.Edit.Components.Menus -{ - public class EditorMenuItemSpacer : EditorMenuItem - { - public EditorMenuItemSpacer() - : base(" ") - { - } - } -} diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 3136faf855..5d0cc64cc3 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -321,7 +321,7 @@ namespace osu.Game.Screens.Edit { undoMenuItem = new EditorMenuItem(CommonStrings.Undo, MenuItemType.Standard, Undo), redoMenuItem = new EditorMenuItem(CommonStrings.Redo, MenuItemType.Standard, Redo), - new EditorMenuItemSpacer(), + new OsuMenuItemSpacer(), cutMenuItem = new EditorMenuItem(CommonStrings.Cut, MenuItemType.Standard, Cut), copyMenuItem = new EditorMenuItem(CommonStrings.Copy, MenuItemType.Standard, Copy), pasteMenuItem = new EditorMenuItem(CommonStrings.Paste, MenuItemType.Standard, Paste), @@ -1005,12 +1005,12 @@ namespace osu.Game.Screens.Edit { createDifficultyCreationMenu(), createDifficultySwitchMenu(), - new EditorMenuItemSpacer(), + new OsuMenuItemSpacer(), new EditorMenuItem(EditorStrings.DeleteDifficulty, MenuItemType.Standard, deleteDifficulty) { Action = { Disabled = Beatmap.Value.BeatmapSetInfo.Beatmaps.Count < 2 } }, - new EditorMenuItemSpacer(), + new OsuMenuItemSpacer(), new EditorMenuItem(WebCommonStrings.ButtonsSave, MenuItemType.Standard, () => Save()), createExportMenu(), - new EditorMenuItemSpacer(), + new OsuMenuItemSpacer(), new EditorMenuItem(CommonStrings.Exit, MenuItemType.Standard, this.Exit) }; @@ -1130,7 +1130,7 @@ namespace osu.Game.Screens.Edit foreach (var rulesetBeatmaps in groupedOrderedBeatmaps) { if (difficultyItems.Count > 0) - difficultyItems.Add(new EditorMenuItemSpacer()); + difficultyItems.Add(new OsuMenuItemSpacer()); foreach (var beatmap in rulesetBeatmaps) { From 9718a802490519780c3d88f55a90037a20b26421 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 21 Nov 2023 14:24:19 +0900 Subject: [PATCH 69/91] Add visible spacer between "inherit" and other curve types --- .../Blueprints/Sliders/Components/PathControlPointVisualiser.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs index f891d23bbd..87e837cc71 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs @@ -369,6 +369,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components if (!selectedPieces.Contains(Pieces[0])) curveTypeItems.Add(createMenuItemForPathType(null)); + curveTypeItems.Add(new OsuMenuItemSpacer()); + // todo: hide/disable items which aren't valid for selected points curveTypeItems.Add(createMenuItemForPathType(PathType.Linear)); curveTypeItems.Add(createMenuItemForPathType(PathType.PerfectCurve)); From c9ee29028fbcf3056c82d5b5c182f862f68974fa Mon Sep 17 00:00:00 2001 From: Samuel Cattini-Schultz Date: Tue, 21 Nov 2023 16:54:20 +1100 Subject: [PATCH 70/91] Fix implicitly used method being named incorrectly --- osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs index 24d5635104..83538a2f42 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs @@ -95,7 +95,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty yield return (ATTRIB_ID_APPROACH_RATE, ApproachRate); yield return (ATTRIB_ID_DIFFICULTY, StarRating); - if (ShouldSerializeFlashlightRating()) + if (ShouldSerializeFlashlightDifficulty()) yield return (ATTRIB_ID_FLASHLIGHT, FlashlightDifficulty); yield return (ATTRIB_ID_SLIDER_FACTOR, SliderFactor); @@ -128,7 +128,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty // unless the fields are also renamed. [UsedImplicitly] - public bool ShouldSerializeFlashlightRating() => Mods.Any(m => m is ModFlashlight); + public bool ShouldSerializeFlashlightDifficulty() => Mods.Any(m => m is ModFlashlight); #endregion } From 3afaafb1d9a19692cbd6750eda24cbd7d920c7d2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 21 Nov 2023 15:05:51 +0900 Subject: [PATCH 71/91] Reorder and simplify private helper methods --- .../TestSceneOpenEditorTimestampInMania.cs | 53 +++++----- .../TestSceneOpenEditorTimestampInOsu.cs | 65 ++++++------ .../Editing/TestSceneOpenEditorTimestamp.cs | 100 +++++++++--------- 3 files changed, 103 insertions(+), 115 deletions(-) diff --git a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneOpenEditorTimestampInMania.cs b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneOpenEditorTimestampInMania.cs index 3c6a9f3b42..05c881d284 100644 --- a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneOpenEditorTimestampInMania.cs +++ b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneOpenEditorTimestampInMania.cs @@ -14,35 +14,6 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor { protected override Ruleset CreateEditorRuleset() => new ManiaRuleset(); - private void addStepClickLink(string timestamp, string step = "", bool displayTimestamp = true) - { - AddStep(displayTimestamp ? $"{step} {timestamp}" : step, () => Editor.HandleTimestamp(timestamp)); - AddUntilStep("wait for seek", () => EditorClock.SeekingOrStopped.Value); - } - - private void addReset() - { - addStepClickLink("00:00:000", "reset", false); - } - - private bool checkSnapAndSelectColumn(double startTime, IReadOnlyCollection<(int, int)>? columnPairs = null) - { - bool checkColumns = columnPairs != null - ? EditorBeatmap.SelectedHitObjects.All(x => columnPairs.Any(col => isNoteAt(x, col.Item1, col.Item2))) - : !EditorBeatmap.SelectedHitObjects.Any(); - - return EditorClock.CurrentTime == startTime - && EditorBeatmap.SelectedHitObjects.Count == (columnPairs?.Count ?? 0) - && checkColumns; - } - - private bool isNoteAt(HitObject hitObject, double time, int column) - { - return hitObject is ManiaHitObject maniaHitObject - && maniaHitObject.StartTime == time - && maniaHitObject.Column == column; - } - [Test] public void TestNormalSelection() { @@ -95,5 +66,29 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor addStepClickLink("00:00:000 (1,2)", "std link"); AddAssert("snap to 1, select none", () => checkSnapAndSelectColumn(2_170)); } + + private void addStepClickLink(string timestamp, string step = "", bool displayTimestamp = true) + { + AddStep(displayTimestamp ? $"{step} {timestamp}" : step, () => Editor.HandleTimestamp(timestamp)); + AddUntilStep("wait for seek", () => EditorClock.SeekingOrStopped.Value); + } + + private void addReset() => addStepClickLink("00:00:000", "reset", false); + + private bool checkSnapAndSelectColumn(double startTime, IReadOnlyCollection<(int, int)>? columnPairs = null) + { + bool checkColumns = columnPairs != null + ? EditorBeatmap.SelectedHitObjects.All(x => columnPairs.Any(col => isNoteAt(x, col.Item1, col.Item2))) + : !EditorBeatmap.SelectedHitObjects.Any(); + + return EditorClock.CurrentTime == startTime + && EditorBeatmap.SelectedHitObjects.Count == (columnPairs?.Count ?? 0) + && checkColumns; + } + + private bool isNoteAt(HitObject hitObject, double time, int column) => + hitObject is ManiaHitObject maniaHitObject + && maniaHitObject.StartTime == time + && maniaHitObject.Column == column; } } diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOpenEditorTimestampInOsu.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOpenEditorTimestampInOsu.cs index 753400e10e..943858652c 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOpenEditorTimestampInOsu.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOpenEditorTimestampInOsu.cs @@ -15,40 +15,6 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor { protected override Ruleset CreateEditorRuleset() => new OsuRuleset(); - private void addStepClickLink(string timestamp, string step = "", bool displayTimestamp = true) - { - AddStep(displayTimestamp ? $"{step} {timestamp}" : step, () => Editor.HandleTimestamp(timestamp)); - AddUntilStep("wait for seek", () => EditorClock.SeekingOrStopped.Value); - } - - private void addReset() - { - addStepClickLink("00:00:000", "reset", false); - } - - private void checkSelection(Func startTime, params int[] comboNumbers) - => AddUntilStep($"seeked & selected {(comboNumbers.Any() ? string.Join(",", comboNumbers) : "nothing")}", () => - { - bool checkCombos = comboNumbers.Any() - ? hasCombosInOrder(EditorBeatmap.SelectedHitObjects, comboNumbers) - : !EditorBeatmap.SelectedHitObjects.Any(); - - return EditorClock.CurrentTime == startTime() - && EditorBeatmap.SelectedHitObjects.Count == comboNumbers.Length - && checkCombos; - }); - - private bool hasCombosInOrder(IEnumerable selected, params int[] comboNumbers) - { - List hitObjects = selected.ToList(); - if (hitObjects.Count != comboNumbers.Length) - return false; - - return !hitObjects.Select(x => (OsuHitObject)x) - .Where((x, i) => x.IndexInCurrentCombo + 1 != comboNumbers[i]) - .Any(); - } - [Test] public void TestNormalSelection() { @@ -90,5 +56,36 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor addStepClickLink("00:00:956 (956|1,956|2)", "mania link"); checkSelection(() => firstObject.StartTime); } + + private void addReset() => addStepClickLink("00:00:000", "reset", false); + + private void addStepClickLink(string timestamp, string step = "", bool displayTimestamp = true) + { + AddStep(displayTimestamp ? $"{step} {timestamp}" : step, () => Editor.HandleTimestamp(timestamp)); + AddUntilStep("wait for seek", () => EditorClock.SeekingOrStopped.Value); + } + + private void checkSelection(Func startTime, params int[] comboNumbers) + => AddUntilStep($"seeked & selected {(comboNumbers.Any() ? string.Join(",", comboNumbers) : "nothing")}", () => + { + bool checkCombos = comboNumbers.Any() + ? hasCombosInOrder(EditorBeatmap.SelectedHitObjects, comboNumbers) + : !EditorBeatmap.SelectedHitObjects.Any(); + + return EditorClock.CurrentTime == startTime() + && EditorBeatmap.SelectedHitObjects.Count == comboNumbers.Length + && checkCombos; + }); + + private bool hasCombosInOrder(IEnumerable selected, params int[] comboNumbers) + { + List hitObjects = selected.ToList(); + if (hitObjects.Count != comboNumbers.Length) + return false; + + return !hitObjects.Select(x => (OsuHitObject)x) + .Where((x, i) => x.IndexInCurrentCombo + 1 != comboNumbers[i]) + .Any(); + } } } diff --git a/osu.Game.Tests/Visual/Editing/TestSceneOpenEditorTimestamp.cs b/osu.Game.Tests/Visual/Editing/TestSceneOpenEditorTimestamp.cs index 86ceb73f7a..dacc5887d0 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneOpenEditorTimestamp.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneOpenEditorTimestamp.cs @@ -25,58 +25,6 @@ namespace osu.Game.Tests.Visual.Editing private EditorBeatmap editorBeatmap => editor.ChildrenOfType().Single(); private EditorClock editorClock => editor.ChildrenOfType().Single(); - private void addStepClickLink(string timestamp, string step = "", bool waitForSeek = true) - { - AddStep($"{step} {timestamp}", () => - Game.HandleLink(new LinkDetails(LinkAction.OpenEditorTimestamp, timestamp)) - ); - - if (waitForSeek) - AddUntilStep("wait for seek", () => editorClock.SeekingOrStopped.Value); - } - - private void addStepScreenModeTo(EditorScreenMode screenMode) - { - AddStep("change screen to " + screenMode, () => editor.Mode.Value = screenMode); - } - - private void assertOnScreenAt(EditorScreenMode screen, double time, string text = "stayed in") - { - AddAssert($"{text} {screen} at {time}", () => - editor.Mode.Value == screen - && editorClock.CurrentTime == time - ); - } - - private void assertMovedScreenTo(EditorScreenMode screen, string text = "moved to") - { - AddAssert($"{text} {screen}", () => editor.Mode.Value == screen); - } - - private void setUpEditor(RulesetInfo ruleset) - { - BeatmapSetInfo beatmapSet = null!; - - AddStep("Import test beatmap", () => - Game.BeatmapManager.Import(TestResources.GetTestBeatmapForImport()).WaitSafely() - ); - AddStep("Retrieve beatmap", () => - beatmapSet = Game.BeatmapManager.QueryBeatmapSet(set => !set.Protected).AsNonNull().Value.Detach() - ); - AddStep("Present beatmap", () => Game.PresentBeatmap(beatmapSet)); - AddUntilStep("Wait for song select", () => - Game.Beatmap.Value.BeatmapSetInfo.Equals(beatmapSet) - && Game.ScreenStack.CurrentScreen is PlaySongSelect songSelect - && songSelect.IsLoaded - ); - AddStep("Switch ruleset", () => Game.Ruleset.Value = ruleset); - AddStep("Open editor for ruleset", () => - ((PlaySongSelect)Game.ScreenStack.CurrentScreen) - .Edit(beatmapSet.Beatmaps.Last(beatmap => beatmap.Ruleset.Name == ruleset.Name)) - ); - AddUntilStep("Wait for editor open", () => editor.ReadyForUse); - } - [Test] public void TestErrorNotifications() { @@ -151,5 +99,53 @@ namespace osu.Game.Tests.Visual.Editing addStepClickLink("00:00:000"); assertOnScreenAt(EditorScreenMode.Compose, 0); } + + private void addStepClickLink(string timestamp, string step = "", bool waitForSeek = true) + { + AddStep($"{step} {timestamp}", () => + Game.HandleLink(new LinkDetails(LinkAction.OpenEditorTimestamp, timestamp)) + ); + + if (waitForSeek) + AddUntilStep("wait for seek", () => editorClock.SeekingOrStopped.Value); + } + + private void addStepScreenModeTo(EditorScreenMode screenMode) => + AddStep("change screen to " + screenMode, () => editor.Mode.Value = screenMode); + + private void assertOnScreenAt(EditorScreenMode screen, double time) + { + AddAssert($"stayed on {screen} at {time}", () => + editor.Mode.Value == screen + && editorClock.CurrentTime == time + ); + } + + private void assertMovedScreenTo(EditorScreenMode screen, string text = "moved to") => + AddAssert($"{text} {screen}", () => editor.Mode.Value == screen); + + private void setUpEditor(RulesetInfo ruleset) + { + BeatmapSetInfo beatmapSet = null!; + + AddStep("Import test beatmap", () => + Game.BeatmapManager.Import(TestResources.GetTestBeatmapForImport()).WaitSafely() + ); + AddStep("Retrieve beatmap", () => + beatmapSet = Game.BeatmapManager.QueryBeatmapSet(set => !set.Protected).AsNonNull().Value.Detach() + ); + AddStep("Present beatmap", () => Game.PresentBeatmap(beatmapSet)); + AddUntilStep("Wait for song select", () => + Game.Beatmap.Value.BeatmapSetInfo.Equals(beatmapSet) + && Game.ScreenStack.CurrentScreen is PlaySongSelect songSelect + && songSelect.IsLoaded + ); + AddStep("Switch ruleset", () => Game.Ruleset.Value = ruleset); + AddStep("Open editor for ruleset", () => + ((PlaySongSelect)Game.ScreenStack.CurrentScreen) + .Edit(beatmapSet.Beatmaps.Last(beatmap => beatmap.Ruleset.Name == ruleset.Name)) + ); + AddUntilStep("Wait for editor open", () => editor.ReadyForUse); + } } } From 917a68eac3273f969a7b760cd7a258e475fcc23e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 21 Nov 2023 15:08:15 +0900 Subject: [PATCH 72/91] Adjust localisablel strings and keys --- .../Visual/Editing/TestSceneOpenEditorTimestamp.cs | 8 ++++---- osu.Game/Localisation/EditorStrings.cs | 6 +++--- osu.Game/OsuGame.cs | 2 +- osu.Game/Screens/Edit/Editor.cs | 2 +- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneOpenEditorTimestamp.cs b/osu.Game.Tests/Visual/Editing/TestSceneOpenEditorTimestamp.cs index dacc5887d0..b6f89ee4e7 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneOpenEditorTimestamp.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneOpenEditorTimestamp.cs @@ -32,7 +32,7 @@ namespace osu.Game.Tests.Visual.Editing addStepClickLink("00:00:000", waitForSeek: false); AddAssert("received 'must be in edit'", - () => Game.Notifications.AllNotifications.Count(x => x.Text == EditorStrings.MustBeInEdit), + () => Game.Notifications.AllNotifications.Count(x => x.Text == EditorStrings.MustBeInEditorToHandleLinks), () => Is.EqualTo(1)); AddStep("enter song select", () => Game.ChildrenOfType().Single().OnSolo.Invoke()); @@ -40,7 +40,7 @@ namespace osu.Game.Tests.Visual.Editing addStepClickLink("00:00:000 (1)", waitForSeek: false); AddAssert("received 'must be in edit'", - () => Game.Notifications.AllNotifications.Count(x => x.Text == EditorStrings.MustBeInEdit), + () => Game.Notifications.AllNotifications.Count(x => x.Text == EditorStrings.MustBeInEditorToHandleLinks), () => Is.EqualTo(2)); setUpEditor(rulesetInfo); @@ -48,12 +48,12 @@ namespace osu.Game.Tests.Visual.Editing addStepClickLink("00:000", "invalid link", waitForSeek: false); AddAssert("received 'failed to process'", - () => Game.Notifications.AllNotifications.Count(x => x.Text == EditorStrings.FailedToProcessTimestamp), + () => Game.Notifications.AllNotifications.Count(x => x.Text == EditorStrings.FailedToParseEditorLink), () => Is.EqualTo(1)); addStepClickLink("50000:00:000", "too long link", waitForSeek: false); AddAssert("received 'failed to process'", - () => Game.Notifications.AllNotifications.Count(x => x.Text == EditorStrings.FailedToProcessTimestamp), + () => Game.Notifications.AllNotifications.Count(x => x.Text == EditorStrings.FailedToParseEditorLink), () => Is.EqualTo(2)); } diff --git a/osu.Game/Localisation/EditorStrings.cs b/osu.Game/Localisation/EditorStrings.cs index e762455e8b..b20b5bc65a 100644 --- a/osu.Game/Localisation/EditorStrings.cs +++ b/osu.Game/Localisation/EditorStrings.cs @@ -122,12 +122,12 @@ namespace osu.Game.Localisation /// /// "Must be in edit mode to handle editor links" /// - public static LocalisableString MustBeInEdit => new TranslatableString(getKey(@"must_be_in_edit"), @"Must be in edit mode to handle editor links"); + public static LocalisableString MustBeInEditorToHandleLinks => new TranslatableString(getKey(@"must_be_in_editor_to_handle_links"), @"Must be in edit mode to handle editor links"); /// - /// "Failed to process timestamp" + /// "Failed to parse editor link" /// - public static LocalisableString FailedToProcessTimestamp => new TranslatableString(getKey(@"failed_to_process_timestamp"), @"Failed to process timestamp"); + public static LocalisableString FailedToParseEditorLink => new TranslatableString(getKey(@"failed_to_parse_edtior_link"), @"Failed to parse editor link"); private static string getKey(string key) => $@"{prefix}:{key}"; } diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 3fda18b7cb..80a9853965 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -565,7 +565,7 @@ namespace osu.Game Schedule(() => Notifications.Post(new SimpleNotification { Icon = FontAwesome.Solid.ExclamationTriangle, - Text = EditorStrings.MustBeInEdit + Text = EditorStrings.MustBeInEditorToHandleLinks })); return; } diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index fb34767315..7dd2666a7e 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -1146,7 +1146,7 @@ namespace osu.Game.Screens.Edit Schedule(() => notifications?.Post(new SimpleNotification { Icon = FontAwesome.Solid.ExclamationTriangle, - Text = EditorStrings.FailedToProcessTimestamp + Text = EditorStrings.FailedToParseEditorLink })); return; } From 7c5345bf7e88ba364cd3606e424f4ae45b6760f2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 21 Nov 2023 15:09:33 +0900 Subject: [PATCH 73/91] Use `SimpleErrorNotification` for error display --- osu.Game/OsuGame.cs | 2 +- osu.Game/Screens/Edit/Editor.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 80a9853965..c4f93636e9 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -562,7 +562,7 @@ namespace osu.Game { if (ScreenStack.CurrentScreen is not Editor editor) { - Schedule(() => Notifications.Post(new SimpleNotification + Schedule(() => Notifications.Post(new SimpleErrorNotification { Icon = FontAwesome.Solid.ExclamationTriangle, Text = EditorStrings.MustBeInEditorToHandleLinks diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 7dd2666a7e..b3cebb41de 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -1143,7 +1143,7 @@ namespace osu.Game.Screens.Edit { if (!EditorTimestampParser.TryParse(timestamp, out var timeSpan, out string selection)) { - Schedule(() => notifications?.Post(new SimpleNotification + Schedule(() => notifications?.Post(new SimpleErrorNotification { Icon = FontAwesome.Solid.ExclamationTriangle, Text = EditorStrings.FailedToParseEditorLink From 314a7bf6f1058195ecf5995fc18a5e5bc1eb94fe Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 21 Nov 2023 11:33:05 +0900 Subject: [PATCH 74/91] Simplify `AddOnce` call to avoid `self` argument --- .../Sliders/SliderPlacementBlueprint.cs | 41 +++++++++---------- 1 file changed, 19 insertions(+), 22 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs index 5f072eb171..32fb7ad351 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs @@ -3,10 +3,12 @@ #nullable disable +using System.Collections.Generic; using System.Diagnostics; using System.Linq; using JetBrains.Annotations; using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Input; using osu.Framework.Input.Events; @@ -84,16 +86,14 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders { freehandToolboxGroup.Tolerance.BindValueChanged(e => { - if (bSplineBuilder.Tolerance != e.NewValue) - bSplineBuilder.Tolerance = e.NewValue; - updateSliderPathFromBSplineBuilder(); + bSplineBuilder.Tolerance = e.NewValue; + Scheduler.AddOnce(updateSliderPathFromBSplineBuilder); }, true); freehandToolboxGroup.CornerThreshold.BindValueChanged(e => { - if (bSplineBuilder.CornerThreshold != e.NewValue) - bSplineBuilder.CornerThreshold = e.NewValue; - updateSliderPathFromBSplineBuilder(); + bSplineBuilder.CornerThreshold = e.NewValue; + Scheduler.AddOnce(updateSliderPathFromBSplineBuilder); }, true); } } @@ -197,27 +197,24 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders base.OnDrag(e); bSplineBuilder.AddLinearPoint(ToLocalSpace(e.ScreenSpaceMousePosition) - HitObject.Position); - updateSliderPathFromBSplineBuilder(); + Scheduler.AddOnce(updateSliderPathFromBSplineBuilder); } private void updateSliderPathFromBSplineBuilder() { - Scheduler.AddOnce(static self => - { - var cps = self.bSplineBuilder.ControlPoints; - var sliderCps = self.HitObject.Path.ControlPoints; - sliderCps.RemoveRange(1, sliderCps.Count - 1); + IReadOnlyList builderPoints = bSplineBuilder.ControlPoints; + BindableList sliderPoints = HitObject.Path.ControlPoints; - // Add the control points from the BSpline builder while converting control points that repeat - // three or more times to a single PathControlPoint with linear type. - for (int i = 1; i < cps.Count; i++) - { - bool isSharp = i < cps.Count - 2 && cps[i] == cps[i + 1] && cps[i] == cps[i + 2]; - sliderCps.Add(new PathControlPoint(cps[i], isSharp ? PathType.BSpline(3) : null)); - if (isSharp) - i += 2; - } - }, this); + sliderPoints.RemoveRange(1, sliderPoints.Count - 1); + + // Add the control points from the BSpline builder while converting control points that repeat + // three or more times to a single PathControlPoint with linear type. + for (int i = 1; i < builderPoints.Count; i++) + { + bool isSharp = i < builderPoints.Count - 2 && builderPoints[i] == builderPoints[i + 1] && builderPoints[i] == builderPoints[i + 2]; + sliderPoints.Add(new PathControlPoint(builderPoints[i], isSharp ? PathType.BSpline(3) : null)); + if (isSharp) i += 2; + } } protected override void OnDragEnd(DragEndEvent e) From 0a5444d091de73c2cc9fe8360146248d1b06b39e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 21 Nov 2023 13:05:31 +0900 Subject: [PATCH 75/91] Fix using the incorrect position for the first point --- .../Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs index 32fb7ad351..68f5b2d70f 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs @@ -180,7 +180,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders return false; bSplineBuilder.Clear(); - bSplineBuilder.AddLinearPoint(ToLocalSpace(e.ScreenSpaceMousePosition) - HitObject.Position); + bSplineBuilder.AddLinearPoint(ToLocalSpace(e.ScreenSpaceMouseDownPosition) - HitObject.Position); setState(SliderPlacementState.Drawing); return true; } From e69e78ad4123a4d89915aa44286a8e637e49429d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 21 Nov 2023 13:10:44 +0900 Subject: [PATCH 76/91] Refactor b-spline path conversion code to better handle linear segments --- .../Sliders/SliderPlacementBlueprint.cs | 44 +++++++++++++++---- 1 file changed, 35 insertions(+), 9 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs index 68f5b2d70f..6e9cc26af4 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs @@ -8,7 +8,6 @@ using System.Diagnostics; using System.Linq; using JetBrains.Annotations; using osu.Framework.Allocation; -using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Input; using osu.Framework.Input.Events; @@ -203,17 +202,44 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders private void updateSliderPathFromBSplineBuilder() { IReadOnlyList builderPoints = bSplineBuilder.ControlPoints; - BindableList sliderPoints = HitObject.Path.ControlPoints; - sliderPoints.RemoveRange(1, sliderPoints.Count - 1); + if (builderPoints.Count == 0) + return; - // Add the control points from the BSpline builder while converting control points that repeat - // three or more times to a single PathControlPoint with linear type. - for (int i = 1; i < builderPoints.Count; i++) + int lastSegmentStart = 0; + PathType? lastPathType = null; + + HitObject.Path.ControlPoints.Clear(); + + // Iterate through generated points, finding each segment and adding non-inheriting path types where appropriate. + // Importantly, the B-Spline builder returns three Vector2s at the same location when a new segment is to be started. + for (int i = 0; i < builderPoints.Count; i++) { - bool isSharp = i < builderPoints.Count - 2 && builderPoints[i] == builderPoints[i + 1] && builderPoints[i] == builderPoints[i + 2]; - sliderPoints.Add(new PathControlPoint(builderPoints[i], isSharp ? PathType.BSpline(3) : null)); - if (isSharp) i += 2; + bool isLastPoint = i == builderPoints.Count - 1; + bool isNewSegment = i < builderPoints.Count - 2 && builderPoints[i] == builderPoints[i + 1] && builderPoints[i] == builderPoints[i + 2]; + + if (isNewSegment || isLastPoint) + { + int pointsInSegment = i - lastSegmentStart; + + // Where possible, we can use the simpler LINEAR path type. + PathType? pathType = pointsInSegment == 1 ? PathType.LINEAR : PathType.BSpline(3); + + // Linear segments can be combined, as two adjacent linear sections are computationally the same as one with the points combined. + if (lastPathType == pathType && lastPathType == PathType.LINEAR) + pathType = null; + + HitObject.Path.ControlPoints.Add(new PathControlPoint(builderPoints[lastSegmentStart], pathType)); + for (int j = lastSegmentStart + 1; j < i; j++) + HitObject.Path.ControlPoints.Add(new PathControlPoint(builderPoints[j])); + + if (isLastPoint) + HitObject.Path.ControlPoints.Add(new PathControlPoint(builderPoints[i])); + + // Skip the redundant duplicated points (see isNewSegment above) which have been coalesced into a path type. + lastSegmentStart = (i += 2); + if (pathType != null) lastPathType = pathType; + } } } From 5175464c18dafc3931b723ae06dbb4375c7c0a77 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 21 Nov 2023 13:35:04 +0900 Subject: [PATCH 77/91] Update test coverage (and add test coverage of curve drawing) --- .../TestSceneSliderPlacementBlueprint.cs | 37 ++++++++++++++++--- 1 file changed, 32 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderPlacementBlueprint.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderPlacementBlueprint.cs index c7a21ba689..8498263138 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderPlacementBlueprint.cs @@ -3,6 +3,7 @@ #nullable disable +using System; using NUnit.Framework; using osu.Framework.Utils; using osu.Game.Rulesets.Edit; @@ -274,7 +275,30 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor } [Test] - public void TestSliderDrawing() + public void TestSliderDrawingCurve() + { + Vector2 startPoint = new Vector2(200); + + addMovementStep(startPoint); + AddStep("press left button", () => InputManager.PressButton(MouseButton.Left)); + + for (int i = 0; i < 20; i++) + addMovementStep(startPoint + new Vector2(i * 40, MathF.Sin(i * MathF.PI / 5) * 50)); + + AddStep("release left button", () => InputManager.ReleaseButton(MouseButton.Left)); + + assertPlaced(true); + assertLength(760, tolerance: 10); + assertControlPointCount(5); + assertControlPointType(0, PathType.BSpline(3)); + assertControlPointType(1, null); + assertControlPointType(2, null); + assertControlPointType(3, null); + assertControlPointType(4, null); + } + + [Test] + public void TestSliderDrawingLinear() { addMovementStep(new Vector2(200)); AddStep("press left button", () => InputManager.PressButton(MouseButton.Left)); @@ -291,7 +315,10 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor assertPlaced(true); assertLength(600, tolerance: 10); assertControlPointCount(4); - assertControlPointType(0, PathType.BSpline(3)); + assertControlPointType(0, PathType.LINEAR); + assertControlPointType(1, null); + assertControlPointType(2, null); + assertControlPointType(3, null); } [Test] @@ -401,11 +428,11 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor private void assertPlaced(bool expected) => AddAssert($"slider {(expected ? "placed" : "not placed")}", () => (getSlider() != null) == expected); - private void assertLength(double expected, double tolerance = 1) => AddAssert($"slider length is {expected}±{tolerance}", () => Precision.AlmostEquals(expected, getSlider().Distance, tolerance)); + private void assertLength(double expected, double tolerance = 1) => AddAssert($"slider length is {expected}±{tolerance}", () => getSlider().Distance, () => Is.EqualTo(expected).Within(tolerance)); - private void assertControlPointCount(int expected) => AddAssert($"has {expected} control points", () => getSlider().Path.ControlPoints.Count == expected); + private void assertControlPointCount(int expected) => AddAssert($"has {expected} control points", () => getSlider().Path.ControlPoints.Count, () => Is.EqualTo(expected)); - private void assertControlPointType(int index, PathType type) => AddAssert($"control point {index} is {type}", () => getSlider().Path.ControlPoints[index].Type == type); + private void assertControlPointType(int index, PathType? type) => AddAssert($"control point {index} is {type?.ToString() ?? "inherit"}", () => getSlider().Path.ControlPoints[index].Type, () => Is.EqualTo(type)); private void assertControlPointPosition(int index, Vector2 position) => AddAssert($"control point {index} at {position}", () => Precision.AlmostEquals(position, getSlider().Path.ControlPoints[index].Position, 1)); From 7bedbe42642384a20b681220b507fcc737325c1e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 21 Nov 2023 13:36:06 +0900 Subject: [PATCH 78/91] Apply NRT to `SliderPlacementBlueprint` tests --- .../Editor/TestSceneSliderPlacementBlueprint.cs | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderPlacementBlueprint.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderPlacementBlueprint.cs index 8498263138..75778b3a7e 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderPlacementBlueprint.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using NUnit.Framework; using osu.Framework.Utils; @@ -428,16 +426,16 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor private void assertPlaced(bool expected) => AddAssert($"slider {(expected ? "placed" : "not placed")}", () => (getSlider() != null) == expected); - private void assertLength(double expected, double tolerance = 1) => AddAssert($"slider length is {expected}±{tolerance}", () => getSlider().Distance, () => Is.EqualTo(expected).Within(tolerance)); + private void assertLength(double expected, double tolerance = 1) => AddAssert($"slider length is {expected}±{tolerance}", () => getSlider()!.Distance, () => Is.EqualTo(expected).Within(tolerance)); - private void assertControlPointCount(int expected) => AddAssert($"has {expected} control points", () => getSlider().Path.ControlPoints.Count, () => Is.EqualTo(expected)); + private void assertControlPointCount(int expected) => AddAssert($"has {expected} control points", () => getSlider()!.Path.ControlPoints.Count, () => Is.EqualTo(expected)); - private void assertControlPointType(int index, PathType? type) => AddAssert($"control point {index} is {type?.ToString() ?? "inherit"}", () => getSlider().Path.ControlPoints[index].Type, () => Is.EqualTo(type)); + private void assertControlPointType(int index, PathType? type) => AddAssert($"control point {index} is {type?.ToString() ?? "inherit"}", () => getSlider()!.Path.ControlPoints[index].Type, () => Is.EqualTo(type)); private void assertControlPointPosition(int index, Vector2 position) => - AddAssert($"control point {index} at {position}", () => Precision.AlmostEquals(position, getSlider().Path.ControlPoints[index].Position, 1)); + AddAssert($"control point {index} at {position}", () => Precision.AlmostEquals(position, getSlider()!.Path.ControlPoints[index].Position, 1)); - private Slider getSlider() => HitObjectContainer.Count > 0 ? ((DrawableSlider)HitObjectContainer[0]).HitObject : null; + private Slider? getSlider() => HitObjectContainer.Count > 0 ? ((DrawableSlider)HitObjectContainer[0]).HitObject : null; protected override DrawableHitObject CreateHitObject(HitObject hitObject) => new DrawableSlider((Slider)hitObject); protected override PlacementBlueprint CreateBlueprint() => new SliderPlacementBlueprint(); From 9c3f9db31838295cccc0ea4f771933dbb4fa2dc7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 21 Nov 2023 14:02:08 +0900 Subject: [PATCH 79/91] Add failing test coverage of BSpline encoding parse failure --- .../Formats/LegacyBeatmapEncoderTest.cs | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs index 0dd277dc89..e847b61fbe 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs @@ -113,6 +113,33 @@ namespace osu.Game.Tests.Beatmaps.Formats Assert.IsTrue(areComboColoursEqual(expected.skin.Configuration, actual.skin.Configuration)); } + [Test] + public void TestEncodeBSplineCurveType() + { + var beatmap = new Beatmap + { + HitObjects = + { + new Slider + { + Path = new SliderPath(new[] + { + new PathControlPoint(Vector2.Zero, PathType.BSpline(3)), + new PathControlPoint(new Vector2(50)), + new PathControlPoint(new Vector2(100), PathType.BSpline(3)), + new PathControlPoint(new Vector2(150)) + }) + }, + } + }; + + var decodedAfterEncode = decodeFromLegacy(encodeToLegacy((beatmap, new TestLegacySkin(beatmaps_resource_store, string.Empty))), string.Empty); + var decodedSlider = (Slider)decodedAfterEncode.beatmap.HitObjects[0]; + Assert.That(decodedSlider.Path.ControlPoints.Count, Is.EqualTo(4)); + Assert.That(decodedSlider.Path.ControlPoints[0].Type, Is.EqualTo(PathType.BSpline(3))); + Assert.That(decodedSlider.Path.ControlPoints[2].Type, Is.EqualTo(PathType.BSpline(3))); + } + [Test] public void TestEncodeMultiSegmentSliderWithFloatingPointError() { From 3d094f84add9f669b2bf0e1bd41bd83da4fd01fd Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 21 Nov 2023 14:02:15 +0900 Subject: [PATCH 80/91] Fix incorrect parsing of BSpline curve types --- osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs index 411a9b0d63..f9e32fe26f 100644 --- a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs @@ -273,8 +273,8 @@ namespace osu.Game.Rulesets.Objects.Legacy while (++endIndex < pointSplit.Length) { - // Keep incrementing endIndex while it's not the start of a new segment (indicated by having a type descriptor of length 1). - if (pointSplit[endIndex].Length > 1) + // Keep incrementing endIndex while it's not the start of a new segment (indicated by having an alpha character at position 0). + if (!char.IsLetter(pointSplit[endIndex][0])) continue; // Multi-segmented sliders DON'T contain the end point as part of the current segment as it's assumed to be the start of the next segment. From ba6fbbe43c122413c4b4b81eb9450e77f3361f8b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 21 Nov 2023 14:03:45 +0900 Subject: [PATCH 81/91] Update framework --- osu.Android.props | 2 +- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index 8175869405..aa993485f3 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -10,7 +10,7 @@ true - + diff --git a/osu.iOS.props b/osu.iOS.props index a5425ba4c7..b43cb1b3f1 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -23,6 +23,6 @@ iossimulator-x64 - + From 92728ea56460dfe0c2135d9165ee8d35e49fe8ad Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 21 Nov 2023 14:10:48 +0900 Subject: [PATCH 82/91] Simplify toolbox initialisation --- osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs index 061f72d72f..65acdb61ae 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs @@ -97,12 +97,12 @@ namespace osu.Game.Rulesets.Osu.Edit // we may be entering the screen with a selection already active updateDistanceSnapGrid(); - RightToolbox.Add(new TransformToolboxGroup - { - RotationHandler = BlueprintContainer.SelectionHandler.RotationHandler, - }); - - RightToolbox.Add(FreehandlSliderToolboxGroup); + RightToolbox.AddRange(new EditorToolboxGroup[] + { + new TransformToolboxGroup { RotationHandler = BlueprintContainer.SelectionHandler.RotationHandler, }, + FreehandlSliderToolboxGroup + } + ); } protected override ComposeBlueprintContainer CreateBlueprintContainer() From cf6f66b84f729a71e04a34b7d9acf9091dfbd7cb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 21 Nov 2023 14:37:06 +0900 Subject: [PATCH 83/91] Remove redundant `Clear()` call --- .../Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs index 6e9cc26af4..32397330c6 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs @@ -178,7 +178,6 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders if (lastCp != cursor && HitObject.Path.ControlPoints.Count == 2) return false; - bSplineBuilder.Clear(); bSplineBuilder.AddLinearPoint(ToLocalSpace(e.ScreenSpaceMouseDownPosition) - HitObject.Position); setState(SliderPlacementState.Drawing); return true; From 016de7be6a9c0a5709e966b6f2bb45349d8f38e8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 21 Nov 2023 14:51:09 +0900 Subject: [PATCH 84/91] Simplify drag handling code in `SliderPlacementBlueprint` --- .../Sliders/SliderPlacementBlueprint.cs | 47 +++++++------------ 1 file changed, 17 insertions(+), 30 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs index 32397330c6..bb4558171f 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs @@ -73,7 +73,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders controlPointVisualiser = new PathControlPointVisualiser(HitObject, false) }; - setState(SliderPlacementState.Initial); + state = SliderPlacementState.Initial; } protected override void LoadComplete() @@ -164,38 +164,30 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders protected override bool OnDragStart(DragStartEvent e) { - if (e.Button == MouseButton.Left) - { - switch (state) - { - case SliderPlacementState.Initial: - return true; + if (e.Button != MouseButton.Left) + return base.OnDragStart(e); - case SliderPlacementState.ControlPoints: - if (HitObject.Path.ControlPoints.Count < 3) - { - var lastCp = HitObject.Path.ControlPoints.LastOrDefault(); - if (lastCp != cursor && HitObject.Path.ControlPoints.Count == 2) - return false; + if (state != SliderPlacementState.ControlPoints) + return base.OnDragStart(e); - bSplineBuilder.AddLinearPoint(ToLocalSpace(e.ScreenSpaceMouseDownPosition) - HitObject.Position); - setState(SliderPlacementState.Drawing); - return true; - } + // Only enter drawing mode if no additional control points have been placed. + if (HitObject.Path.ControlPoints.Count > 2) + return base.OnDragStart(e); - return false; - } - } - - return base.OnDragStart(e); + bSplineBuilder.AddLinearPoint(ToLocalSpace(e.ScreenSpaceMouseDownPosition) - HitObject.Position); + state = SliderPlacementState.Drawing; + return true; } protected override void OnDrag(DragEvent e) { base.OnDrag(e); - bSplineBuilder.AddLinearPoint(ToLocalSpace(e.ScreenSpaceMousePosition) - HitObject.Position); - Scheduler.AddOnce(updateSliderPathFromBSplineBuilder); + if (state == SliderPlacementState.Drawing) + { + bSplineBuilder.AddLinearPoint(ToLocalSpace(e.ScreenSpaceMousePosition) - HitObject.Position); + Scheduler.AddOnce(updateSliderPathFromBSplineBuilder); + } } private void updateSliderPathFromBSplineBuilder() @@ -260,7 +252,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders private void beginCurve() { BeginPlacement(commitStart: true); - setState(SliderPlacementState.ControlPoints); + state = SliderPlacementState.ControlPoints; } private void endCurve() @@ -357,11 +349,6 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders tailCirclePiece.UpdateFrom(HitObject.TailCircle); } - private void setState(SliderPlacementState newState) - { - state = newState; - } - private enum SliderPlacementState { Initial, From a210469956ca340bb302f76679c49142e2e0ae69 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 21 Nov 2023 14:52:21 +0900 Subject: [PATCH 85/91] Reorder methods --- .../Sliders/SliderPlacementBlueprint.cs | 106 +++++++++--------- 1 file changed, 53 insertions(+), 53 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs index bb4558171f..397d6ffb91 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs @@ -190,50 +190,6 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders } } - private void updateSliderPathFromBSplineBuilder() - { - IReadOnlyList builderPoints = bSplineBuilder.ControlPoints; - - if (builderPoints.Count == 0) - return; - - int lastSegmentStart = 0; - PathType? lastPathType = null; - - HitObject.Path.ControlPoints.Clear(); - - // Iterate through generated points, finding each segment and adding non-inheriting path types where appropriate. - // Importantly, the B-Spline builder returns three Vector2s at the same location when a new segment is to be started. - for (int i = 0; i < builderPoints.Count; i++) - { - bool isLastPoint = i == builderPoints.Count - 1; - bool isNewSegment = i < builderPoints.Count - 2 && builderPoints[i] == builderPoints[i + 1] && builderPoints[i] == builderPoints[i + 2]; - - if (isNewSegment || isLastPoint) - { - int pointsInSegment = i - lastSegmentStart; - - // Where possible, we can use the simpler LINEAR path type. - PathType? pathType = pointsInSegment == 1 ? PathType.LINEAR : PathType.BSpline(3); - - // Linear segments can be combined, as two adjacent linear sections are computationally the same as one with the points combined. - if (lastPathType == pathType && lastPathType == PathType.LINEAR) - pathType = null; - - HitObject.Path.ControlPoints.Add(new PathControlPoint(builderPoints[lastSegmentStart], pathType)); - for (int j = lastSegmentStart + 1; j < i; j++) - HitObject.Path.ControlPoints.Add(new PathControlPoint(builderPoints[j])); - - if (isLastPoint) - HitObject.Path.ControlPoints.Add(new PathControlPoint(builderPoints[i])); - - // Skip the redundant duplicated points (see isNewSegment above) which have been coalesced into a path type. - lastSegmentStart = (i += 2); - if (pathType != null) lastPathType = pathType; - } - } - } - protected override void OnDragEnd(DragEndEvent e) { base.OnDragEnd(e); @@ -249,6 +205,15 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders base.OnMouseUp(e); } + protected override void Update() + { + base.Update(); + updateSlider(); + + // Maintain the path type in case it got defaulted to bezier at some point during the drag. + updatePathType(); + } + private void beginCurve() { BeginPlacement(commitStart: true); @@ -261,15 +226,6 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders EndPlacement(true); } - protected override void Update() - { - base.Update(); - updateSlider(); - - // Maintain the path type in case it got defaulted to bezier at some point during the drag. - updatePathType(); - } - private void updatePathType() { if (state == SliderPlacementState.Drawing) @@ -349,6 +305,50 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders tailCirclePiece.UpdateFrom(HitObject.TailCircle); } + private void updateSliderPathFromBSplineBuilder() + { + IReadOnlyList builderPoints = bSplineBuilder.ControlPoints; + + if (builderPoints.Count == 0) + return; + + int lastSegmentStart = 0; + PathType? lastPathType = null; + + HitObject.Path.ControlPoints.Clear(); + + // Iterate through generated points, finding each segment and adding non-inheriting path types where appropriate. + // Importantly, the B-Spline builder returns three Vector2s at the same location when a new segment is to be started. + for (int i = 0; i < builderPoints.Count; i++) + { + bool isLastPoint = i == builderPoints.Count - 1; + bool isNewSegment = i < builderPoints.Count - 2 && builderPoints[i] == builderPoints[i + 1] && builderPoints[i] == builderPoints[i + 2]; + + if (isNewSegment || isLastPoint) + { + int pointsInSegment = i - lastSegmentStart; + + // Where possible, we can use the simpler LINEAR path type. + PathType? pathType = pointsInSegment == 1 ? PathType.LINEAR : PathType.BSpline(3); + + // Linear segments can be combined, as two adjacent linear sections are computationally the same as one with the points combined. + if (lastPathType == pathType && lastPathType == PathType.LINEAR) + pathType = null; + + HitObject.Path.ControlPoints.Add(new PathControlPoint(builderPoints[lastSegmentStart], pathType)); + for (int j = lastSegmentStart + 1; j < i; j++) + HitObject.Path.ControlPoints.Add(new PathControlPoint(builderPoints[j])); + + if (isLastPoint) + HitObject.Path.ControlPoints.Add(new PathControlPoint(builderPoints[i])); + + // Skip the redundant duplicated points (see isNewSegment above) which have been coalesced into a path type. + lastSegmentStart = (i += 2); + if (pathType != null) lastPathType = pathType; + } + } + } + private enum SliderPlacementState { Initial, From 1660eb3c15b1075e93a64a2938e8e86451f5df84 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 21 Nov 2023 15:31:24 +0900 Subject: [PATCH 86/91] Add failing test coverage of drag after point placement --- .../TestSceneSliderPlacementBlueprint.cs | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderPlacementBlueprint.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderPlacementBlueprint.cs index 75778b3a7e..7ac34bc6c8 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderPlacementBlueprint.cs @@ -272,6 +272,30 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor assertControlPointType(2, PathType.PERFECT_CURVE); } + [Test] + public void TestSliderDrawingDoesntActivateAfterNormalPlacement() + { + Vector2 startPoint = new Vector2(200); + + addMovementStep(startPoint); + addClickStep(MouseButton.Left); + + for (int i = 0; i < 20; i++) + { + if (i == 5) + AddStep("press left button", () => InputManager.PressButton(MouseButton.Left)); + addMovementStep(startPoint + new Vector2(i * 40, MathF.Sin(i * MathF.PI / 5) * 50)); + } + + AddStep("release left button", () => InputManager.ReleaseButton(MouseButton.Left)); + assertPlaced(false); + + addClickStep(MouseButton.Right); + assertPlaced(true); + + assertControlPointType(0, PathType.BEZIER); + } + [Test] public void TestSliderDrawingCurve() { From cc33e121258adb0d607fba5c6bf9897e1979eac5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 21 Nov 2023 15:16:40 +0900 Subject: [PATCH 87/91] Fix dragging after one point already placed incorrectly entering drawing mode --- .../Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs index 397d6ffb91..63370350ed 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs @@ -171,7 +171,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders return base.OnDragStart(e); // Only enter drawing mode if no additional control points have been placed. - if (HitObject.Path.ControlPoints.Count > 2) + int controlPointCount = HitObject.Path.ControlPoints.Count; + if (controlPointCount > 2 || (controlPointCount == 2 && HitObject.Path.ControlPoints.Last() != cursor)) return base.OnDragStart(e); bSplineBuilder.AddLinearPoint(ToLocalSpace(e.ScreenSpaceMouseDownPosition) - HitObject.Position); From 4b2d8aa6a647b28c13f63dc3047cca04b8b8c398 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 21 Nov 2023 15:31:39 +0900 Subject: [PATCH 88/91] Add `ToString` on `PathType` for better test output --- osu.Game/Rulesets/Objects/Types/PathType.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Rulesets/Objects/Types/PathType.cs b/osu.Game/Rulesets/Objects/Types/PathType.cs index f84d43e3e7..23f1ccf0bc 100644 --- a/osu.Game/Rulesets/Objects/Types/PathType.cs +++ b/osu.Game/Rulesets/Objects/Types/PathType.cs @@ -81,5 +81,7 @@ namespace osu.Game.Rulesets.Objects.Types public static bool operator ==(PathType a, PathType b) => a.Equals(b); public static bool operator !=(PathType a, PathType b) => !a.Equals(b); + + public override string ToString() => Description; } } From ed3874682365596f96fac6c8622821786b3645a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 21 Nov 2023 16:14:30 +0900 Subject: [PATCH 89/91] Fix spacer appearing on top of menu --- .../Sliders/Components/PathControlPointVisualiser.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs index 87e837cc71..3a80d04ab8 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs @@ -367,9 +367,10 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components List curveTypeItems = new List(); if (!selectedPieces.Contains(Pieces[0])) + { curveTypeItems.Add(createMenuItemForPathType(null)); - - curveTypeItems.Add(new OsuMenuItemSpacer()); + curveTypeItems.Add(new OsuMenuItemSpacer()); + } // todo: hide/disable items which aren't valid for selected points curveTypeItems.Add(createMenuItemForPathType(PathType.Linear)); From c98be5823db0818bf5eaf8bcab853fa8622d5257 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 22 Nov 2023 07:52:28 +0900 Subject: [PATCH 90/91] Update framework --- osu.Android.props | 2 +- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index aa993485f3..ea08992710 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -10,7 +10,7 @@ true - + diff --git a/osu.iOS.props b/osu.iOS.props index b43cb1b3f1..53d5d6b010 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -23,6 +23,6 @@ iossimulator-x64 - + From aa724070653dc41df9e67c8476427752586b219f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 22 Nov 2023 07:55:39 +0900 Subject: [PATCH 91/91] Fix android compile failures due to invalid java version See https://github.com/ppy/osu-framework/pull/6057. --- .github/workflows/ci.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a8167ec4db..11c956bb71 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -108,6 +108,12 @@ jobs: - name: Checkout uses: actions/checkout@v3 + - name: Setup JDK 11 + uses: actions/setup-java@v3 + with: + distribution: microsoft + java-version: 11 + - name: Install .NET 6.0.x uses: actions/setup-dotnet@v3 with: