From 9811c46e3573e8299fed6a326422704fd3aa3e53 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 26 Nov 2020 19:16:18 +0900 Subject: [PATCH 01/11] Rename application method to better describe what it actually does --- .../Edit/Blueprints/HoldNotePlacementBlueprint.cs | 4 ++-- .../Edit/Blueprints/ManiaPlacementBlueprint.cs | 4 ++-- .../Edit/Blueprints/NotePlacementBlueprint.cs | 4 ++-- .../Edit/Blueprints/HitCircles/HitCirclePlacementBlueprint.cs | 4 ++-- .../Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs | 4 ++-- .../Edit/Blueprints/HitPlacementBlueprint.cs | 4 ++-- .../Edit/Blueprints/TaikoSpanPlacementBlueprint.cs | 4 ++-- osu.Game/Rulesets/Edit/PlacementBlueprint.cs | 2 +- .../Edit/Compose/Components/ComposeBlueprintContainer.cs | 2 +- osu.Game/Tests/Visual/PlacementBlueprintTestScene.cs | 4 ++-- 10 files changed, 18 insertions(+), 18 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNotePlacementBlueprint.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNotePlacementBlueprint.cs index b5ec1e1a2a..1f92929392 100644 --- a/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNotePlacementBlueprint.cs +++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNotePlacementBlueprint.cs @@ -78,9 +78,9 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints private double originalStartTime; - public override void UpdatePosition(SnapResult result) + public override void UpdateTimeAndPosition(SnapResult result) { - base.UpdatePosition(result); + base.UpdateTimeAndPosition(result); if (PlacementActive) { diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaPlacementBlueprint.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaPlacementBlueprint.cs index 27a279e044..5e09054667 100644 --- a/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaPlacementBlueprint.cs @@ -48,9 +48,9 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints return true; } - public override void UpdatePosition(SnapResult result) + public override void UpdateTimeAndPosition(SnapResult result) { - base.UpdatePosition(result); + base.UpdateTimeAndPosition(result); if (!PlacementActive) Column = result.Playfield as Column; diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/NotePlacementBlueprint.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/NotePlacementBlueprint.cs index 684004b558..3db89c8ae6 100644 --- a/osu.Game.Rulesets.Mania/Edit/Blueprints/NotePlacementBlueprint.cs +++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/NotePlacementBlueprint.cs @@ -22,9 +22,9 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints InternalChild = piece = new EditNotePiece { Origin = Anchor.Centre }; } - public override void UpdatePosition(SnapResult result) + public override void UpdateTimeAndPosition(SnapResult result) { - base.UpdatePosition(result); + base.UpdateTimeAndPosition(result); if (result.Playfield != null) { diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/HitCircles/HitCirclePlacementBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/HitCircles/HitCirclePlacementBlueprint.cs index e14d6647d2..c45a04053f 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/HitCircles/HitCirclePlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/HitCircles/HitCirclePlacementBlueprint.cs @@ -45,9 +45,9 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles return base.OnMouseDown(e); } - public override void UpdatePosition(SnapResult result) + public override void UpdateTimeAndPosition(SnapResult result) { - base.UpdatePosition(result); + base.UpdateTimeAndPosition(result); HitObject.Position = ToLocalSpace(result.ScreenSpacePosition); } } diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs index 4b99cc23ed..b71e1914f7 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs @@ -67,9 +67,9 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders inputManager = GetContainingInputManager(); } - public override void UpdatePosition(SnapResult result) + public override void UpdateTimeAndPosition(SnapResult result) { - base.UpdatePosition(result); + base.UpdateTimeAndPosition(result); switch (state) { diff --git a/osu.Game.Rulesets.Taiko/Edit/Blueprints/HitPlacementBlueprint.cs b/osu.Game.Rulesets.Taiko/Edit/Blueprints/HitPlacementBlueprint.cs index c5191ab241..17e7fb81f6 100644 --- a/osu.Game.Rulesets.Taiko/Edit/Blueprints/HitPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Taiko/Edit/Blueprints/HitPlacementBlueprint.cs @@ -43,10 +43,10 @@ namespace osu.Game.Rulesets.Taiko.Edit.Blueprints return false; } - public override void UpdatePosition(SnapResult result) + public override void UpdateTimeAndPosition(SnapResult result) { piece.Position = ToLocalSpace(result.ScreenSpacePosition); - base.UpdatePosition(result); + base.UpdateTimeAndPosition(result); } } } diff --git a/osu.Game.Rulesets.Taiko/Edit/Blueprints/TaikoSpanPlacementBlueprint.cs b/osu.Game.Rulesets.Taiko/Edit/Blueprints/TaikoSpanPlacementBlueprint.cs index 468d980b23..e53b331f46 100644 --- a/osu.Game.Rulesets.Taiko/Edit/Blueprints/TaikoSpanPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Taiko/Edit/Blueprints/TaikoSpanPlacementBlueprint.cs @@ -68,9 +68,9 @@ namespace osu.Game.Rulesets.Taiko.Edit.Blueprints EndPlacement(true); } - public override void UpdatePosition(SnapResult result) + public override void UpdateTimeAndPosition(SnapResult result) { - base.UpdatePosition(result); + base.UpdateTimeAndPosition(result); if (PlacementActive) { diff --git a/osu.Game/Rulesets/Edit/PlacementBlueprint.cs b/osu.Game/Rulesets/Edit/PlacementBlueprint.cs index d986b71380..9d7dc7b8f7 100644 --- a/osu.Game/Rulesets/Edit/PlacementBlueprint.cs +++ b/osu.Game/Rulesets/Edit/PlacementBlueprint.cs @@ -88,7 +88,7 @@ namespace osu.Game.Rulesets.Edit /// Updates the position of this to a new screen-space position. /// /// The snap result information. - public virtual void UpdatePosition(SnapResult result) + public virtual void UpdateTimeAndPosition(SnapResult result) { if (!PlacementActive) HitObject.StartTime = result.Time ?? EditorClock?.CurrentTime ?? Time.Current; diff --git a/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs index 0d2e2360b1..dddfd763d5 100644 --- a/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs @@ -157,7 +157,7 @@ namespace osu.Game.Screens.Edit.Compose.Components { var snapResult = Composer.SnapScreenSpacePositionToValidTime(inputManager.CurrentState.Mouse.Position); - currentPlacement.UpdatePosition(snapResult); + currentPlacement.UpdateTimeAndPosition(snapResult); } #endregion diff --git a/osu.Game/Tests/Visual/PlacementBlueprintTestScene.cs b/osu.Game/Tests/Visual/PlacementBlueprintTestScene.cs index c3d74f21aa..78a6bcc3db 100644 --- a/osu.Game/Tests/Visual/PlacementBlueprintTestScene.cs +++ b/osu.Game/Tests/Visual/PlacementBlueprintTestScene.cs @@ -72,7 +72,7 @@ namespace osu.Game.Tests.Visual { base.Update(); - currentBlueprint.UpdatePosition(SnapForBlueprint(currentBlueprint)); + currentBlueprint.UpdateTimeAndPosition(SnapForBlueprint(currentBlueprint)); } protected virtual SnapResult SnapForBlueprint(PlacementBlueprint blueprint) => @@ -85,7 +85,7 @@ namespace osu.Game.Tests.Visual if (drawable is PlacementBlueprint blueprint) { blueprint.Show(); - blueprint.UpdatePosition(SnapForBlueprint(blueprint)); + blueprint.UpdateTimeAndPosition(SnapForBlueprint(blueprint)); } } From 91592cf32d387d332610a03f46175105ac78ccd0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 26 Nov 2020 19:20:39 +0900 Subject: [PATCH 02/11] Expose EditorClock for consumption --- .../Screens/Edit/Compose/Components/BlueprintContainer.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs index def5f396f1..5c5f203667 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs @@ -41,7 +41,7 @@ namespace osu.Game.Screens.Edit.Compose.Components private IEditorChangeHandler changeHandler { get; set; } [Resolved] - private EditorClock editorClock { get; set; } + protected EditorClock EditorClock { get; private set; } [Resolved] protected EditorBeatmap Beatmap { get; private set; } @@ -170,7 +170,7 @@ namespace osu.Game.Screens.Edit.Compose.Components if (clickedBlueprint == null || SelectionHandler.SelectedBlueprints.FirstOrDefault(b => b.IsHovered) != clickedBlueprint) return false; - editorClock?.SeekTo(clickedBlueprint.HitObject.StartTime); + EditorClock?.SeekTo(clickedBlueprint.HitObject.StartTime); return true; } @@ -381,7 +381,7 @@ namespace osu.Game.Screens.Edit.Compose.Components case SelectionState.Selected: // if the editor is playing, we generally don't want to deselect objects even if outside the selection area. - if (!editorClock.IsRunning && !isValidForSelection()) + if (!EditorClock.IsRunning && !isValidForSelection()) blueprint.Deselect(); break; } From da6bccc8125725a06bc7d82fb1d34ae0886e0456 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 26 Nov 2020 19:20:51 +0900 Subject: [PATCH 03/11] Apply beat snap if positional snap doesn't give a time result --- .../Edit/Compose/Components/ComposeBlueprintContainer.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs index dddfd763d5..1893366d90 100644 --- a/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs @@ -157,6 +157,9 @@ namespace osu.Game.Screens.Edit.Compose.Components { var snapResult = Composer.SnapScreenSpacePositionToValidTime(inputManager.CurrentState.Mouse.Position); + // if no time was found from positional snapping, we should still quantize to the beat. + snapResult.Time ??= Beatmap.SnapTime(EditorClock.CurrentTime, null); + currentPlacement.UpdateTimeAndPosition(snapResult); } From ab1ad99c88f556afb8c1daeb2ffa849c2597e451 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 26 Nov 2020 19:33:51 +0900 Subject: [PATCH 04/11] Fix failing test scene (was previously not snapped properly) --- .../Editor/TestSceneObjectObjectSnap.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneObjectObjectSnap.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneObjectObjectSnap.cs index 6b532e5014..7bdf131e0d 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneObjectObjectSnap.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneObjectObjectSnap.cs @@ -25,6 +25,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor { base.SetUpSteps(); AddStep("get playfield", () => playfield = Editor.ChildrenOfType().First()); + AddStep("seek to first control point", () => EditorClock.Seek(Beatmap.Value.Beatmap.ControlPointInfo.TimingPoints.First().Time)); } [TestCase(true)] @@ -66,13 +67,13 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor AddStep("start slider placement", () => InputManager.Click(MouseButton.Left)); - AddStep("move to place end", () => InputManager.MoveMouseTo(playfield.ScreenSpaceDrawQuad.Centre + new Vector2(playfield.ScreenSpaceDrawQuad.Width * 0.185f, 0))); + AddStep("move to place end", () => InputManager.MoveMouseTo(playfield.ScreenSpaceDrawQuad.Centre + new Vector2(playfield.ScreenSpaceDrawQuad.Width * 0.225f, 0))); AddStep("end slider placement", () => InputManager.Click(MouseButton.Right)); AddStep("enter circle placement mode", () => InputManager.Key(Key.Number2)); - AddStep("move mouse slightly", () => InputManager.MoveMouseTo(playfield.ScreenSpaceDrawQuad.Centre + new Vector2(playfield.ScreenSpaceDrawQuad.Width * 0.20f, 0))); + AddStep("move mouse slightly", () => InputManager.MoveMouseTo(playfield.ScreenSpaceDrawQuad.Centre + new Vector2(playfield.ScreenSpaceDrawQuad.Width * 0.235f, 0))); AddStep("place second object", () => InputManager.Click(MouseButton.Left)); From 9a08cc8c04d8a45747736bcea22db930e53e3096 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 26 Nov 2020 19:40:10 +0900 Subject: [PATCH 05/11] Add test coverage of beat snapping hit circles --- .../Editor/TestSceneBeatSnap.cs | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 osu.Game.Rulesets.Osu.Tests/Editor/TestSceneBeatSnap.cs diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneBeatSnap.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneBeatSnap.cs new file mode 100644 index 0000000000..a652fb32f4 --- /dev/null +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneBeatSnap.cs @@ -0,0 +1,41 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Linq; +using NUnit.Framework; +using osu.Framework.Testing; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Osu.UI; +using osu.Game.Tests.Beatmaps; +using osuTK.Input; + +namespace osu.Game.Rulesets.Osu.Tests.Editor +{ + [TestFixture] + public class TestSceneObjectBeatSnap : TestSceneOsuEditor + { + private OsuPlayfield playfield; + + protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) => new TestBeatmap(Ruleset.Value, false); + + public override void SetUpSteps() + { + base.SetUpSteps(); + AddStep("get playfield", () => playfield = Editor.ChildrenOfType().First()); + } + + [Test] + public void TestBeatSnapHitCircle() + { + double firstTimingPointTime() => Beatmap.Value.Beatmap.ControlPointInfo.TimingPoints.First().Time; + + AddStep("seek some milliseconds forward", () => EditorClock.Seek(firstTimingPointTime() + 10)); + + AddStep("move mouse to centre", () => InputManager.MoveMouseTo(playfield.ScreenSpaceDrawQuad.Centre)); + AddStep("enter placement mode", () => InputManager.Key(Key.Number2)); + AddStep("place first object", () => InputManager.Click(MouseButton.Left)); + + AddAssert("ensure object snapped back to correct time", () => EditorBeatmap.HitObjects.First().StartTime == firstTimingPointTime()); + } + } +} From 203c36f7202f4fb869c548fc1d71b5d89d4e4fbc Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 26 Nov 2020 19:46:54 +0900 Subject: [PATCH 06/11] Rename file to match test name --- .../Editor/{TestSceneBeatSnap.cs => TestSceneObjectBeatSnap.cs} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename osu.Game.Rulesets.Osu.Tests/Editor/{TestSceneBeatSnap.cs => TestSceneObjectBeatSnap.cs} (100%) diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneBeatSnap.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneObjectBeatSnap.cs similarity index 100% rename from osu.Game.Rulesets.Osu.Tests/Editor/TestSceneBeatSnap.cs rename to osu.Game.Rulesets.Osu.Tests/Editor/TestSceneObjectBeatSnap.cs From fe48b2279c39dee39de905c63e052bc88c6134d2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 30 Nov 2020 17:43:53 +0900 Subject: [PATCH 07/11] Adjust various paddings and spacings in settings to make them easier to visually parse --- osu.Game/Overlays/Settings/SettingsSection.cs | 15 +++++++++------ osu.Game/Overlays/Settings/SettingsSubsection.cs | 4 ++-- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/osu.Game/Overlays/Settings/SettingsSection.cs b/osu.Game/Overlays/Settings/SettingsSection.cs index 97e4ba9da7..8b4821398f 100644 --- a/osu.Game/Overlays/Settings/SettingsSection.cs +++ b/osu.Game/Overlays/Settings/SettingsSection.cs @@ -26,7 +26,7 @@ namespace osu.Game.Overlays.Settings public virtual IEnumerable FilterTerms => new[] { Header }; private const int header_size = 26; - private const int header_margin = 25; + private const int margin = 20; private const int border_size = 2; public bool MatchingFilter @@ -38,7 +38,7 @@ namespace osu.Game.Overlays.Settings protected SettingsSection() { - Margin = new MarginPadding { Top = 20 }; + Margin = new MarginPadding { Top = margin }; AutoSizeAxes = Axes.Y; RelativeSizeAxes = Axes.X; @@ -46,10 +46,9 @@ namespace osu.Game.Overlays.Settings { Margin = new MarginPadding { - Top = header_size + header_margin + Top = header_size }, Direction = FillDirection.Vertical, - Spacing = new Vector2(0, 30), AutoSizeAxes = Axes.Y, RelativeSizeAxes = Axes.X, }; @@ -70,7 +69,7 @@ namespace osu.Game.Overlays.Settings { Padding = new MarginPadding { - Top = 20 + border_size, + Top = margin + border_size, Bottom = 10, }, RelativeSizeAxes = Axes.X, @@ -82,7 +81,11 @@ namespace osu.Game.Overlays.Settings Font = OsuFont.GetFont(size: header_size), Text = Header, Colour = colours.Yellow, - Margin = new MarginPadding { Left = SettingsPanel.CONTENT_MARGINS, Right = SettingsPanel.CONTENT_MARGINS } + Margin = new MarginPadding + { + Left = SettingsPanel.CONTENT_MARGINS, + Right = SettingsPanel.CONTENT_MARGINS + } }, FlowContent } diff --git a/osu.Game/Overlays/Settings/SettingsSubsection.cs b/osu.Game/Overlays/Settings/SettingsSubsection.cs index b096c146a6..1b82d973e9 100644 --- a/osu.Game/Overlays/Settings/SettingsSubsection.cs +++ b/osu.Game/Overlays/Settings/SettingsSubsection.cs @@ -39,7 +39,7 @@ namespace osu.Game.Overlays.Settings FlowContent = new FillFlowContainer { Direction = FillDirection.Vertical, - Spacing = new Vector2(0, 5), + Spacing = new Vector2(0, 8), RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, }; @@ -53,7 +53,7 @@ namespace osu.Game.Overlays.Settings new OsuSpriteText { Text = Header.ToUpperInvariant(), - Margin = new MarginPadding { Bottom = 10, Left = SettingsPanel.CONTENT_MARGINS, Right = SettingsPanel.CONTENT_MARGINS }, + Margin = new MarginPadding { Vertical = 30, Left = SettingsPanel.CONTENT_MARGINS, Right = SettingsPanel.CONTENT_MARGINS }, Font = OsuFont.GetFont(weight: FontWeight.Bold), }, FlowContent From 965cc1f511568b07231c1b189ebd9066d339568e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 30 Nov 2020 17:57:25 +0900 Subject: [PATCH 08/11] Remove unnecessary usings #2 --- osu.Game/Overlays/Settings/SettingsSection.cs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/osu.Game/Overlays/Settings/SettingsSection.cs b/osu.Game/Overlays/Settings/SettingsSection.cs index 8b4821398f..4143605c28 100644 --- a/osu.Game/Overlays/Settings/SettingsSection.cs +++ b/osu.Game/Overlays/Settings/SettingsSection.cs @@ -1,16 +1,15 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using osuTK; -using osuTK.Graphics; +using System.Collections.Generic; +using System.Linq; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; -using System.Collections.Generic; -using System.Linq; +using osuTK.Graphics; namespace osu.Game.Overlays.Settings { From 3ad2eeaff551b279e5db2b33ac0d0a0087544d1f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 30 Nov 2020 18:35:30 +0900 Subject: [PATCH 09/11] Fix outdated xmldoc --- osu.Game/Rulesets/Edit/PlacementBlueprint.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Edit/PlacementBlueprint.cs b/osu.Game/Rulesets/Edit/PlacementBlueprint.cs index 9d7dc7b8f7..c0eb891f5e 100644 --- a/osu.Game/Rulesets/Edit/PlacementBlueprint.cs +++ b/osu.Game/Rulesets/Edit/PlacementBlueprint.cs @@ -85,7 +85,7 @@ namespace osu.Game.Rulesets.Edit } /// - /// Updates the position of this to a new screen-space position. + /// Updates the time and position of this based on the provided snap information. /// /// The snap result information. public virtual void UpdateTimeAndPosition(SnapResult result) From 5760e1c1fca35e580b459ad6a05431efc50a975f Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 1 Dec 2020 15:37:51 +0900 Subject: [PATCH 10/11] Make HitSampleInfo immutable --- .../TestSceneAutoJuiceStream.cs | 2 +- osu.Game.Rulesets.Catch/Objects/Banana.cs | 5 + .../Objects/JuiceStream.cs | 7 +- .../TestSceneSlider.cs | 10 +- .../Objects/Drawables/DrawableSlider.cs | 3 +- .../Objects/Drawables/DrawableSpinner.cs | 3 +- osu.Game.Rulesets.Osu/Objects/Slider.cs | 9 +- .../Objects/SpinnerBonusTick.cs | 2 +- .../Objects/Drawables/DrawableHit.cs | 6 +- .../Drawables/DrawableTaikoHitObject.cs | 2 +- .../Editing/LegacyEditorBeatmapPatcherTest.cs | 8 +- osu.Game/Audio/HitSampleInfo.cs | 38 +++++-- .../ControlPoints/SampleControlPoint.cs | 14 +-- .../Beatmaps/Formats/LegacyBeatmapEncoder.cs | 2 +- osu.Game/Beatmaps/Formats/LegacyDecoder.cs | 7 +- .../Objects/Legacy/ConvertHitObjectParser.cs | 101 +++++++----------- .../Components/ComposeBlueprintContainer.cs | 4 +- .../Compose/Components/SelectionHandler.cs | 2 +- osu.Game/Utils/Optional.cs | 45 ++++++++ 19 files changed, 144 insertions(+), 126 deletions(-) create mode 100644 osu.Game/Utils/Optional.cs diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneAutoJuiceStream.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneAutoJuiceStream.cs index 3c636a5b97..0d57fb7029 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneAutoJuiceStream.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneAutoJuiceStream.cs @@ -43,7 +43,7 @@ namespace osu.Game.Rulesets.Catch.Tests NewCombo = i % 8 == 0, Samples = new List(new[] { - new HitSampleInfo { Bank = "normal", Name = "hitnormal", Volume = 100 } + new HitSampleInfo(HitSampleInfo.HIT_NORMAL, "normal") }) }); } diff --git a/osu.Game.Rulesets.Catch/Objects/Banana.cs b/osu.Game.Rulesets.Catch/Objects/Banana.cs index ccb1fff15b..822db890e2 100644 --- a/osu.Game.Rulesets.Catch/Objects/Banana.cs +++ b/osu.Game.Rulesets.Catch/Objects/Banana.cs @@ -55,6 +55,11 @@ namespace osu.Game.Rulesets.Catch.Objects private static string[] lookupNames { get; } = { "metronomelow", "catch-banana" }; public override IEnumerable LookupNames => lookupNames; + + public BananaHitSampleInfo() + : base(string.Empty) + { + } } } } diff --git a/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs b/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs index e209d012fa..d5819935ad 100644 --- a/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs +++ b/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs @@ -50,12 +50,7 @@ namespace osu.Game.Rulesets.Catch.Objects { base.CreateNestedHitObjects(cancellationToken); - var dropletSamples = Samples.Select(s => new HitSampleInfo - { - Bank = s.Bank, - Name = @"slidertick", - Volume = s.Volume - }).ToList(); + var dropletSamples = Samples.Select(s => s.With(@"slidertick")).ToList(); int nodeIndex = 0; SliderEventDescriptor? lastEvent = null; diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSlider.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSlider.cs index c400e2f2ea..d40484f5ed 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSlider.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSlider.cs @@ -108,8 +108,8 @@ namespace osu.Game.Rulesets.Osu.Tests AddStep("change samples", () => slider.HitObject.Samples = new[] { - new HitSampleInfo { Name = HitSampleInfo.HIT_CLAP }, - new HitSampleInfo { Name = HitSampleInfo.HIT_WHISTLE }, + new HitSampleInfo(HitSampleInfo.HIT_CLAP), + new HitSampleInfo(HitSampleInfo.HIT_WHISTLE), }); AddAssert("head samples updated", () => assertSamples(slider.HitObject.HeadCircle)); @@ -136,15 +136,15 @@ namespace osu.Game.Rulesets.Osu.Tests slider = (DrawableSlider)createSlider(repeats: 1); for (int i = 0; i < 2; i++) - slider.HitObject.NodeSamples.Add(new List { new HitSampleInfo { Name = HitSampleInfo.HIT_FINISH } }); + slider.HitObject.NodeSamples.Add(new List { new HitSampleInfo(HitSampleInfo.HIT_FINISH) }); Add(slider); }); AddStep("change samples", () => slider.HitObject.Samples = new[] { - new HitSampleInfo { Name = HitSampleInfo.HIT_CLAP }, - new HitSampleInfo { Name = HitSampleInfo.HIT_WHISTLE }, + new HitSampleInfo(HitSampleInfo.HIT_CLAP), + new HitSampleInfo(HitSampleInfo.HIT_WHISTLE), }); AddAssert("head samples not updated", () => assertSamples(slider.HitObject.HeadCircle)); diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs index dd27ac990e..3aa0c9ccb5 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs @@ -115,8 +115,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables if (firstSample != null) { - var clone = HitObject.SampleControlPoint.ApplyTo(firstSample); - clone.Name = "sliderslide"; + var clone = HitObject.SampleControlPoint.ApplyTo(firstSample).With("sliderslide"); samplesContainer.Add(slidingSample = new PausableSkinnableSound(clone) { diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs index 2a14a7c975..5a11265a47 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs @@ -110,8 +110,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables if (firstSample != null) { - var clone = HitObject.SampleControlPoint.ApplyTo(firstSample); - clone.Name = "spinnerspin"; + var clone = HitObject.SampleControlPoint.ApplyTo(firstSample).With("spinnerspin"); samplesContainer.Add(spinningSample = new PausableSkinnableSound(clone) { diff --git a/osu.Game.Rulesets.Osu/Objects/Slider.cs b/osu.Game.Rulesets.Osu/Objects/Slider.cs index 755ce0866a..1670df24a8 100644 --- a/osu.Game.Rulesets.Osu/Objects/Slider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Slider.cs @@ -221,14 +221,7 @@ namespace osu.Game.Rulesets.Osu.Objects var sampleList = new List(); if (firstSample != null) - { - sampleList.Add(new HitSampleInfo - { - Bank = firstSample.Bank, - Volume = firstSample.Volume, - Name = @"slidertick", - }); - } + sampleList.Add(firstSample.With("slidertick")); foreach (var tick in NestedHitObjects.OfType()) tick.Samples = sampleList; diff --git a/osu.Game.Rulesets.Osu/Objects/SpinnerBonusTick.cs b/osu.Game.Rulesets.Osu/Objects/SpinnerBonusTick.cs index 235dc8710a..2c443cb96b 100644 --- a/osu.Game.Rulesets.Osu/Objects/SpinnerBonusTick.cs +++ b/osu.Game.Rulesets.Osu/Objects/SpinnerBonusTick.cs @@ -11,7 +11,7 @@ namespace osu.Game.Rulesets.Osu.Objects { public SpinnerBonusTick() { - Samples.Add(new HitSampleInfo { Name = "spinnerbonus" }); + Samples.Add(new HitSampleInfo("spinnerbonus")); } public override Judgement CreateJudgement() => new OsuSpinnerBonusTickJudgement(); diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs index 4a3759794b..29a96a7a40 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs @@ -79,7 +79,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables if (isRimType != rimSamples.Any()) { if (isRimType) - HitObject.Samples.Add(new HitSampleInfo { Name = HitSampleInfo.HIT_CLAP }); + HitObject.Samples.Add(new HitSampleInfo(HitSampleInfo.HIT_CLAP)); else { foreach (var sample in rimSamples) @@ -125,9 +125,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables if (s.Name != HitSampleInfo.HIT_FINISH) continue; - var sClone = s.Clone(); - sClone.Name = HitSampleInfo.HIT_WHISTLE; - corrected[i] = sClone; + corrected[i] = s.With(HitSampleInfo.HIT_WHISTLE); } return corrected; diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs index d8d75a7614..ff5b221273 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs @@ -169,7 +169,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables if (isStrong.Value != strongSamples.Any()) { if (isStrong.Value) - HitObject.Samples.Add(new HitSampleInfo { Name = HitSampleInfo.HIT_FINISH }); + HitObject.Samples.Add(new HitSampleInfo(HitSampleInfo.HIT_FINISH)); else { foreach (var sample in strongSamples) diff --git a/osu.Game.Tests/Editing/LegacyEditorBeatmapPatcherTest.cs b/osu.Game.Tests/Editing/LegacyEditorBeatmapPatcherTest.cs index bb56131b04..44a908b756 100644 --- a/osu.Game.Tests/Editing/LegacyEditorBeatmapPatcherTest.cs +++ b/osu.Game.Tests/Editing/LegacyEditorBeatmapPatcherTest.cs @@ -139,7 +139,7 @@ namespace osu.Game.Tests.Editing HitObjects = { (OsuHitObject)current.HitObjects[0], - new HitCircle { StartTime = 2000, Samples = { new HitSampleInfo { Name = HitSampleInfo.HIT_FINISH } } }, + new HitCircle { StartTime = 2000, Samples = { new HitSampleInfo(HitSampleInfo.HIT_FINISH) } }, (OsuHitObject)current.HitObjects[2], } }; @@ -268,12 +268,12 @@ namespace osu.Game.Tests.Editing HitObjects = { (OsuHitObject)current.HitObjects[0], - new HitCircle { StartTime = 1000, Samples = { new HitSampleInfo { Name = HitSampleInfo.HIT_FINISH } } }, + new HitCircle { StartTime = 1000, Samples = { new HitSampleInfo(HitSampleInfo.HIT_FINISH) } }, (OsuHitObject)current.HitObjects[2], (OsuHitObject)current.HitObjects[3], - new HitCircle { StartTime = 2250, Samples = { new HitSampleInfo { Name = HitSampleInfo.HIT_WHISTLE } } }, + new HitCircle { StartTime = 2250, Samples = { new HitSampleInfo(HitSampleInfo.HIT_WHISTLE) } }, (OsuHitObject)current.HitObjects[5], - new HitCircle { StartTime = 3000, Samples = { new HitSampleInfo { Name = HitSampleInfo.HIT_CLAP } } }, + new HitCircle { StartTime = 3000, Samples = { new HitSampleInfo(HitSampleInfo.HIT_CLAP) } }, (OsuHitObject)current.HitObjects[7], } }; diff --git a/osu.Game/Audio/HitSampleInfo.cs b/osu.Game/Audio/HitSampleInfo.cs index 8efaeb3795..bf4a83e5d7 100644 --- a/osu.Game/Audio/HitSampleInfo.cs +++ b/osu.Game/Audio/HitSampleInfo.cs @@ -1,8 +1,11 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +#nullable enable + using System; using System.Collections.Generic; +using osu.Game.Utils; namespace osu.Game.Audio { @@ -22,25 +25,33 @@ namespace osu.Game.Audio /// public static IEnumerable AllAdditions => new[] { HIT_WHISTLE, HIT_CLAP, HIT_FINISH }; - /// - /// The bank to load the sample from. - /// - public string Bank; - /// /// The name of the sample to load. /// - public string Name; + public readonly string Name; + + /// + /// The bank to load the sample from. + /// + public readonly string? Bank; /// /// An optional suffix to provide priority lookup. Falls back to non-suffixed . /// - public string Suffix; + public readonly string? Suffix; /// /// The sample volume. /// - public int Volume { get; set; } + public int Volume { get; } + + public HitSampleInfo(string name, string? bank = null, string? suffix = null, int volume = 100) + { + Name = name; + Bank = bank; + Suffix = suffix; + Volume = volume; + } /// /// Retrieve all possible filenames that can be used as a source, returned in order of preference (highest first). @@ -56,6 +67,15 @@ namespace osu.Game.Audio } } - public HitSampleInfo Clone() => (HitSampleInfo)MemberwiseClone(); + /// + /// Creates a new with overridden values. + /// + /// An optional new sample name. + /// An optional new sample bank. + /// An optional new lookup suffix. + /// An optional new volume. + /// The new . + public virtual HitSampleInfo With(Optional name = default, Optional bank = default, Optional suffix = default, Optional volume = default) + => new HitSampleInfo(name.GetOr(Name), bank.GetOr(Bank), suffix.GetOr(Suffix), volume.GetOr(Volume)); } } diff --git a/osu.Game/Beatmaps/ControlPoints/SampleControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/SampleControlPoint.cs index f57ecfb9e3..8064da1543 100644 --- a/osu.Game/Beatmaps/ControlPoints/SampleControlPoint.cs +++ b/osu.Game/Beatmaps/ControlPoints/SampleControlPoint.cs @@ -58,12 +58,7 @@ namespace osu.Game.Beatmaps.ControlPoints /// /// The name of the same. /// A populated . - public HitSampleInfo GetSampleInfo(string sampleName = HitSampleInfo.HIT_NORMAL) => new HitSampleInfo - { - Bank = SampleBank, - Name = sampleName, - Volume = SampleVolume, - }; + public HitSampleInfo GetSampleInfo(string sampleName = HitSampleInfo.HIT_NORMAL) => new HitSampleInfo(sampleName, SampleBank, volume: SampleVolume); /// /// Applies and to a if necessary, returning the modified . @@ -71,12 +66,7 @@ namespace osu.Game.Beatmaps.ControlPoints /// The . This will not be modified. /// The modified . This does not share a reference with . public virtual HitSampleInfo ApplyTo(HitSampleInfo hitSampleInfo) - { - var newSampleInfo = hitSampleInfo.Clone(); - newSampleInfo.Bank = hitSampleInfo.Bank ?? SampleBank; - newSampleInfo.Volume = hitSampleInfo.Volume > 0 ? hitSampleInfo.Volume : SampleVolume; - return newSampleInfo; - } + => hitSampleInfo.With(bank: hitSampleInfo.Bank ?? SampleBank, volume: hitSampleInfo.Volume > 0 ? hitSampleInfo.Volume : SampleVolume); public override bool IsRedundant(ControlPoint existing) => existing is SampleControlPoint existingSample diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs index 7ddb0b4caa..df940e8c8e 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs @@ -192,7 +192,7 @@ namespace osu.Game.Beatmaps.Formats var effectPoint = beatmap.ControlPointInfo.EffectPointAt(time); // Apply the control point to a hit sample to uncover legacy properties (e.g. suffix) - HitSampleInfo tempHitSample = samplePoint.ApplyTo(new ConvertHitObjectParser.LegacyHitSampleInfo()); + HitSampleInfo tempHitSample = samplePoint.ApplyTo(new ConvertHitObjectParser.LegacyHitSampleInfo(string.Empty)); // Convert effect flags to the legacy format LegacyEffectFlags effectFlags = LegacyEffectFlags.None; diff --git a/osu.Game/Beatmaps/Formats/LegacyDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyDecoder.cs index de4dc8cdc8..9f16180e77 100644 --- a/osu.Game/Beatmaps/Formats/LegacyDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyDecoder.cs @@ -182,11 +182,8 @@ namespace osu.Game.Beatmaps.Formats { var baseInfo = base.ApplyTo(hitSampleInfo); - if (baseInfo is ConvertHitObjectParser.LegacyHitSampleInfo legacy - && legacy.CustomSampleBank == 0) - { - legacy.CustomSampleBank = CustomSampleBank; - } + if (baseInfo is ConvertHitObjectParser.LegacyHitSampleInfo legacy && legacy.CustomSampleBank == 0) + return legacy.With(customSampleBank: CustomSampleBank); return baseInfo; } diff --git a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs index 44b22033dc..9b38266400 100644 --- a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs @@ -13,6 +13,7 @@ using JetBrains.Annotations; using osu.Framework.Utils; using osu.Game.Beatmaps.Legacy; using osu.Game.Skinning; +using osu.Game.Utils; namespace osu.Game.Rulesets.Objects.Legacy { @@ -427,62 +428,25 @@ namespace osu.Game.Rulesets.Objects.Legacy // Todo: This should return the normal SampleInfos if the specified sample file isn't found, but that's a pretty edge-case scenario if (!string.IsNullOrEmpty(bankInfo.Filename)) { - return new List - { - new FileHitSampleInfo - { - Filename = bankInfo.Filename, - Volume = bankInfo.Volume - } - }; + return new List { new FileHitSampleInfo(bankInfo.Filename, bankInfo.Volume) }; } var soundTypes = new List { - new LegacyHitSampleInfo - { - Bank = bankInfo.Normal, - Name = HitSampleInfo.HIT_NORMAL, - Volume = bankInfo.Volume, - CustomSampleBank = bankInfo.CustomSampleBank, + new LegacyHitSampleInfo(HitSampleInfo.HIT_NORMAL, bankInfo.Normal, bankInfo.Volume, bankInfo.CustomSampleBank, // if the sound type doesn't have the Normal flag set, attach it anyway as a layered sample. // None also counts as a normal non-layered sample: https://osu.ppy.sh/help/wiki/osu!_File_Formats/Osu_(file_format)#hitsounds - IsLayered = type != LegacyHitSoundType.None && !type.HasFlag(LegacyHitSoundType.Normal) - } + type != LegacyHitSoundType.None && !type.HasFlag(LegacyHitSoundType.Normal)) }; if (type.HasFlag(LegacyHitSoundType.Finish)) - { - soundTypes.Add(new LegacyHitSampleInfo - { - Bank = bankInfo.Add, - Name = HitSampleInfo.HIT_FINISH, - Volume = bankInfo.Volume, - CustomSampleBank = bankInfo.CustomSampleBank - }); - } + soundTypes.Add(new LegacyHitSampleInfo(HitSampleInfo.HIT_FINISH, bankInfo.Add, bankInfo.Volume, bankInfo.CustomSampleBank)); if (type.HasFlag(LegacyHitSoundType.Whistle)) - { - soundTypes.Add(new LegacyHitSampleInfo - { - Bank = bankInfo.Add, - Name = HitSampleInfo.HIT_WHISTLE, - Volume = bankInfo.Volume, - CustomSampleBank = bankInfo.CustomSampleBank - }); - } + soundTypes.Add(new LegacyHitSampleInfo(HitSampleInfo.HIT_WHISTLE, bankInfo.Add, bankInfo.Volume, bankInfo.CustomSampleBank)); if (type.HasFlag(LegacyHitSoundType.Clap)) - { - soundTypes.Add(new LegacyHitSampleInfo - { - Bank = bankInfo.Add, - Name = HitSampleInfo.HIT_CLAP, - Volume = bankInfo.Volume, - CustomSampleBank = bankInfo.CustomSampleBank - }); - } + soundTypes.Add(new LegacyHitSampleInfo(HitSampleInfo.HIT_CLAP, bankInfo.Add, bankInfo.Volume, bankInfo.CustomSampleBank)); return soundTypes; } @@ -500,21 +464,11 @@ namespace osu.Game.Rulesets.Objects.Legacy public SampleBankInfo Clone() => (SampleBankInfo)MemberwiseClone(); } +#nullable enable + public class LegacyHitSampleInfo : HitSampleInfo { - private int customSampleBank; - - public int CustomSampleBank - { - get => customSampleBank; - set - { - customSampleBank = value; - - if (value >= 2) - Suffix = value.ToString(); - } - } + public readonly int CustomSampleBank; /// /// Whether this hit sample is layered. @@ -523,18 +477,33 @@ namespace osu.Game.Rulesets.Objects.Legacy /// Layered hit samples are automatically added in all modes (except osu!mania), but can be disabled /// using the skin config option. /// - public bool IsLayered { get; set; } + public readonly bool IsLayered; + + public LegacyHitSampleInfo([NotNull] string name, string? bank = null, int volume = 100, int customSampleBank = 0, bool isLayered = false) + : base(name, bank, customSampleBank >= 2 ? customSampleBank.ToString() : null, volume) + { + CustomSampleBank = customSampleBank; + IsLayered = isLayered; + } + + public override HitSampleInfo With(Optional name = default, Optional bank = default, Optional suffix = default, Optional volume = default) + => With(name, bank, volume); + + public LegacyHitSampleInfo With(Optional name = default, Optional bank = default, Optional volume = default, Optional customSampleBank = default, + Optional isLayered = default) + => new LegacyHitSampleInfo(name.GetOr(Name), bank.GetOr(Bank), volume.GetOr(Volume), customSampleBank.GetOr(CustomSampleBank), isLayered.GetOr(IsLayered)); } private class FileHitSampleInfo : LegacyHitSampleInfo { - public string Filename; + public readonly string Filename; - public FileHitSampleInfo() - { - // Make sure that the LegacyBeatmapSkin does not fall back to the user skin. + public FileHitSampleInfo(string filename, int volume) + // Force CSS=1 to make sure that the LegacyBeatmapSkin does not fall back to the user skin. // Note that this does not change the lookup names, as they are overridden locally. - CustomSampleBank = 1; + : base(string.Empty, customSampleBank: 1, volume: volume) + { + Filename = filename; } public override IEnumerable LookupNames => new[] @@ -542,6 +511,14 @@ namespace osu.Game.Rulesets.Objects.Legacy Filename, Path.ChangeExtension(Filename, null) }; + + public override HitSampleInfo With(Optional name = default, Optional bank = default, Optional suffix = default, Optional volume = default) + => With(volume: volume); + + public FileHitSampleInfo With(Optional filename = default, Optional volume = default) + => new FileHitSampleInfo(filename.GetOr(Filename), volume.GetOr(Volume)); } + +#nullable disable } } diff --git a/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs index 1893366d90..c09b935f28 100644 --- a/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs @@ -101,7 +101,7 @@ namespace osu.Game.Screens.Edit.Compose.Components case TernaryState.True: if (existingSample == null) - samples.Add(new HitSampleInfo { Name = sampleName }); + samples.Add(new HitSampleInfo(sampleName)); break; } } @@ -212,7 +212,7 @@ namespace osu.Game.Screens.Edit.Compose.Components if (blueprint != null) { // doing this post-creations as adding the default hit sample should be the case regardless of the ruleset. - blueprint.HitObject.Samples.Add(new HitSampleInfo { Name = HitSampleInfo.HIT_NORMAL }); + blueprint.HitObject.Samples.Add(new HitSampleInfo(HitSampleInfo.HIT_NORMAL)); placementBlueprintContainer.Child = currentPlacement = blueprint; diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs index adf22a3370..788b485449 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs @@ -328,7 +328,7 @@ namespace osu.Game.Screens.Edit.Compose.Components if (h.Samples.Any(s => s.Name == sampleName)) continue; - h.Samples.Add(new HitSampleInfo { Name = sampleName }); + h.Samples.Add(new HitSampleInfo(sampleName)); } EditorBeatmap.EndChange(); diff --git a/osu.Game/Utils/Optional.cs b/osu.Game/Utils/Optional.cs new file mode 100644 index 0000000000..9f8a1c2e62 --- /dev/null +++ b/osu.Game/Utils/Optional.cs @@ -0,0 +1,45 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +#nullable enable + +namespace osu.Game.Utils +{ + /// + /// A wrapper over a value and a boolean denoting whether the value is valid. + /// + /// The type of value stored. + public readonly ref struct Optional + { + /// + /// The stored value. + /// + public readonly T Value; + + /// + /// Whether is valid. + /// + /// + /// If is a reference type, null may be valid for . + /// + public readonly bool HasValue; + + private Optional(T value) + { + Value = value; + HasValue = true; + } + + /// + /// Returns if it's valid, or a given fallback value otherwise. + /// + /// + /// Shortcase for: optional.HasValue ? optional.Value : fallback. + /// + /// The fallback value to return if is false. + /// + public T GetOr(T fallback) => HasValue ? Value : fallback; + + public static implicit operator Optional(T value) => new Optional(value); + } +} From 199710b941a396cfed43b989a127c5a355522dfa Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 1 Dec 2020 15:44:16 +0900 Subject: [PATCH 11/11] Implement equality comparers for HitSampleInfo --- osu.Game.Rulesets.Catch/Objects/Banana.cs | 15 ++++++++++++--- osu.Game/Audio/HitSampleInfo.cs | 10 +++++++++- .../Objects/Legacy/ConvertHitObjectParser.cs | 19 +++++++++++++++++-- 3 files changed, 38 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Objects/Banana.cs b/osu.Game.Rulesets.Catch/Objects/Banana.cs index 822db890e2..ec4b67f341 100644 --- a/osu.Game.Rulesets.Catch/Objects/Banana.cs +++ b/osu.Game.Rulesets.Catch/Objects/Banana.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 osu.Framework.Utils; using osu.Game.Audio; @@ -50,16 +51,24 @@ namespace osu.Game.Rulesets.Catch.Objects } } - private class BananaHitSampleInfo : HitSampleInfo + private class BananaHitSampleInfo : HitSampleInfo, IEquatable { - private static string[] lookupNames { get; } = { "metronomelow", "catch-banana" }; + private static readonly string[] lookup_names = { "metronomelow", "catch-banana" }; - public override IEnumerable LookupNames => lookupNames; + public override IEnumerable LookupNames => lookup_names; public BananaHitSampleInfo() : base(string.Empty) { } + + public bool Equals(BananaHitSampleInfo other) + => other != null; + + public override bool Equals(object obj) + => Equals((BananaHitSampleInfo)obj); + + public override int GetHashCode() => lookup_names.GetHashCode(); } } } diff --git a/osu.Game/Audio/HitSampleInfo.cs b/osu.Game/Audio/HitSampleInfo.cs index bf4a83e5d7..4a0e48749a 100644 --- a/osu.Game/Audio/HitSampleInfo.cs +++ b/osu.Game/Audio/HitSampleInfo.cs @@ -13,7 +13,7 @@ namespace osu.Game.Audio /// Describes a gameplay hit sample. /// [Serializable] - public class HitSampleInfo : ISampleInfo + public class HitSampleInfo : ISampleInfo, IEquatable { public const string HIT_WHISTLE = @"hitwhistle"; public const string HIT_FINISH = @"hitfinish"; @@ -77,5 +77,13 @@ namespace osu.Game.Audio /// The new . public virtual HitSampleInfo With(Optional name = default, Optional bank = default, Optional suffix = default, Optional volume = default) => new HitSampleInfo(name.GetOr(Name), bank.GetOr(Bank), suffix.GetOr(Suffix), volume.GetOr(Volume)); + + public bool Equals(HitSampleInfo? other) + => other != null && Name == other.Name && Bank == other.Bank && Suffix == other.Suffix; + + public override bool Equals(object? obj) + => Equals((HitSampleInfo?)obj); + + public override int GetHashCode() => HashCode.Combine(Name, Bank, Suffix); } } diff --git a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs index 9b38266400..931bdb3db7 100644 --- a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs @@ -466,7 +466,7 @@ namespace osu.Game.Rulesets.Objects.Legacy #nullable enable - public class LegacyHitSampleInfo : HitSampleInfo + public class LegacyHitSampleInfo : HitSampleInfo, IEquatable { public readonly int CustomSampleBank; @@ -492,9 +492,16 @@ namespace osu.Game.Rulesets.Objects.Legacy public LegacyHitSampleInfo With(Optional name = default, Optional bank = default, Optional volume = default, Optional customSampleBank = default, Optional isLayered = default) => new LegacyHitSampleInfo(name.GetOr(Name), bank.GetOr(Bank), volume.GetOr(Volume), customSampleBank.GetOr(CustomSampleBank), isLayered.GetOr(IsLayered)); + + public bool Equals(LegacyHitSampleInfo? other) + => base.Equals(other) && CustomSampleBank == other.CustomSampleBank && IsLayered == other.IsLayered; + + public override bool Equals(object? obj) => Equals((LegacyHitSampleInfo?)obj); + + public override int GetHashCode() => HashCode.Combine(base.GetHashCode(), CustomSampleBank, IsLayered); } - private class FileHitSampleInfo : LegacyHitSampleInfo + private class FileHitSampleInfo : LegacyHitSampleInfo, IEquatable { public readonly string Filename; @@ -517,6 +524,14 @@ namespace osu.Game.Rulesets.Objects.Legacy public FileHitSampleInfo With(Optional filename = default, Optional volume = default) => new FileHitSampleInfo(filename.GetOr(Filename), volume.GetOr(Volume)); + + public bool Equals(FileHitSampleInfo? other) + => base.Equals(other) && Filename == other.Filename; + + public override bool Equals(object? obj) + => Equals((FileHitSampleInfo?)obj); + + public override int GetHashCode() => HashCode.Combine(base.GetHashCode(), Filename); } #nullable disable