From 6301f837e0b17a8216f2af8a3ee55e064249b16d Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 11 Oct 2019 15:27:23 +0900 Subject: [PATCH 01/19] Initial implementation of osu! beat snapping grid --- .../TestSceneOsuBeatSnapGrid.cs | 170 ++++++++++++++++++ 1 file changed, 170 insertions(+) create mode 100644 osu.Game.Rulesets.Osu.Tests/TestSceneOsuBeatSnapGrid.cs diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneOsuBeatSnapGrid.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuBeatSnapGrid.cs new file mode 100644 index 0000000000..9f0d59afab --- /dev/null +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuBeatSnapGrid.cs @@ -0,0 +1,170 @@ +// 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 NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.UserInterface; +using osu.Framework.Input.Events; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Osu.Beatmaps; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Screens.Edit; +using osu.Game.Screens.Edit.Compose.Components; +using osu.Game.Tests.Visual; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Rulesets.Osu.Tests +{ + public class TestSceneOsuBeatSnapGrid : ManualInputManagerTestScene + { + private const double beat_length = 100; + private static readonly Vector2 grid_position = new Vector2(512, 384); + + [Cached(typeof(IEditorBeatmap))] + private readonly EditorBeatmap editorBeatmap; + + [Cached] + private readonly BindableBeatDivisor beatDivisor = new BindableBeatDivisor(); + + private OsuBeatSnapGrid grid; + private Drawable cursor; + + public TestSceneOsuBeatSnapGrid() + { + editorBeatmap = new EditorBeatmap(new OsuBeatmap()); + } + + [SetUp] + public void Setup() => Schedule(() => + { + Clear(); + + editorBeatmap.ControlPointInfo.TimingPoints.Clear(); + editorBeatmap.ControlPointInfo.TimingPoints.Add(new TimingControlPoint { BeatLength = beat_length }); + + beatDivisor.Value = 1; + }); + + protected override bool OnMouseMove(MouseMoveEvent e) + { + base.OnMouseMove(e); + + if (cursor != null) + cursor.Position = grid?.GetSnapPosition(grid.ToLocalSpace(e.ScreenSpaceMousePosition)) ?? e.ScreenSpaceMousePosition; + + return true; + } + + [TestCase(1)] + [TestCase(2)] + [TestCase(3)] + [TestCase(4)] + [TestCase(6)] + [TestCase(8)] + [TestCase(12)] + [TestCase(16)] + public void TestBeatDivisor(int divisor) + { + AddStep($"set beat divisor = {divisor}", () => beatDivisor.Value = divisor); + createGrid(); + } + + private void createGrid() + { + AddStep("create grid", () => + { + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.SlateGray + }, + grid = new OsuBeatSnapGrid(new HitCircle { Position = grid_position }), + cursor = new Circle + { + Origin = Anchor.Centre, + Size = new Vector2(50), + Colour = Color4.Red + } + }; + }); + } + + private abstract class CircularBeatSnapGrid : BeatSnapGrid + { + protected override void CreateGrid(Vector2 startPosition) + { + float maxDistance = Math.Max( + Vector2.Distance(startPosition, Vector2.Zero), + Math.Max( + Vector2.Distance(startPosition, new Vector2(DrawWidth, 0)), + Math.Max( + Vector2.Distance(startPosition, new Vector2(0, DrawHeight)), + Vector2.Distance(startPosition, DrawSize)))); + + int requiredCircles = (int)(maxDistance / DistanceSpacing); + + for (int i = 0; i < requiredCircles; i++) + { + float radius = (i + 1) * DistanceSpacing * 2; + + AddInternal(new CircularProgress + { + Origin = Anchor.Centre, + Position = startPosition, + Current = { Value = 1 }, + Size = new Vector2(radius), + InnerRadius = 4 * 1f / radius, + Colour = GetColourForBeatIndex(i) + }); + } + } + + public override Vector2 GetSnapPosition(Vector2 position) + { + Vector2 direction = position - StartPosition; + float distance = direction.Length; + + float radius = DistanceSpacing; + int radialCount = Math.Max(1, (int)Math.Round(distance / radius)); + + if (radialCount <= 0) + return position; + + Vector2 normalisedDirection = direction * new Vector2(1f / distance); + + return StartPosition + normalisedDirection * radialCount * radius; + } + } + + private class OsuBeatSnapGrid : CircularBeatSnapGrid + { + /// + /// Scoring distance with a speed-adjusted beat length of 1 second. + /// + private const float base_scoring_distance = 100; + + public OsuBeatSnapGrid(OsuHitObject hitObject) + : base(hitObject, hitObject.StackedEndPosition) + { + } + + protected override float GetVelocity(double time, ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty) + { + TimingControlPoint timingPoint = controlPointInfo.TimingPointAt(time); + DifficultyControlPoint difficultyPoint = controlPointInfo.DifficultyPointAt(time); + + double scoringDistance = base_scoring_distance * difficulty.SliderMultiplier * difficultyPoint.SpeedMultiplier; + + return (float)(scoringDistance / timingPoint.BeatLength); + } + } + } +} From 4d32a8aa6b5cca76f5b993791ce4e536a4709836 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 11 Oct 2019 17:11:37 +0900 Subject: [PATCH 02/19] More tests --- .../TestSceneOsuBeatSnapGrid.cs | 172 +++++++++++++++--- 1 file changed, 145 insertions(+), 27 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneOsuBeatSnapGrid.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuBeatSnapGrid.cs index 9f0d59afab..7baa2f0d72 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneOsuBeatSnapGrid.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuBeatSnapGrid.cs @@ -5,9 +5,11 @@ using System; using NUnit.Framework; using osu.Framework.Allocation; 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.Framework.MathUtils; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Rulesets.Objects; @@ -32,12 +34,13 @@ namespace osu.Game.Rulesets.Osu.Tests [Cached] private readonly BindableBeatDivisor beatDivisor = new BindableBeatDivisor(); - private OsuBeatSnapGrid grid; - private Drawable cursor; + private TestOsuBeatSnapGrid grid; public TestSceneOsuBeatSnapGrid() { editorBeatmap = new EditorBeatmap(new OsuBeatmap()); + + createGrid(); } [SetUp] @@ -45,22 +48,14 @@ namespace osu.Game.Rulesets.Osu.Tests { Clear(); + editorBeatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier = 1; + editorBeatmap.ControlPointInfo.DifficultyPoints.Clear(); editorBeatmap.ControlPointInfo.TimingPoints.Clear(); editorBeatmap.ControlPointInfo.TimingPoints.Add(new TimingControlPoint { BeatLength = beat_length }); beatDivisor.Value = 1; }); - protected override bool OnMouseMove(MouseMoveEvent e) - { - base.OnMouseMove(e); - - if (cursor != null) - cursor.Position = grid?.GetSnapPosition(grid.ToLocalSpace(e.ScreenSpaceMousePosition)) ?? e.ScreenSpaceMousePosition; - - return true; - } - [TestCase(1)] [TestCase(2)] [TestCase(3)] @@ -75,6 +70,80 @@ namespace osu.Game.Rulesets.Osu.Tests createGrid(); } + [TestCase(100, 100)] + [TestCase(200, 100)] + public void TestBeatLength(float beatLength, float expectedSpacing) + { + AddStep($"set beat length = {beatLength}", () => + { + editorBeatmap.ControlPointInfo.TimingPoints.Clear(); + editorBeatmap.ControlPointInfo.TimingPoints.Add(new TimingControlPoint { BeatLength = beatLength }); + }); + + createGrid(); + AddAssert($"spacing = {expectedSpacing}", () => Precision.AlmostEquals(expectedSpacing, grid.DistanceSpacing)); + } + + [TestCase(0.5f, 50)] + [TestCase(1, 100)] + [TestCase(1.5f, 150)] + public void TestSpeedMultiplier(float multiplier, float expectedSpacing) + { + AddStep($"set speed multiplier = {multiplier}", () => + { + editorBeatmap.ControlPointInfo.DifficultyPoints.Clear(); + editorBeatmap.ControlPointInfo.DifficultyPoints.Add(new DifficultyControlPoint { SpeedMultiplier = multiplier }); + }); + + createGrid(); + AddAssert($"spacing = {expectedSpacing}", () => Precision.AlmostEquals(expectedSpacing, grid.DistanceSpacing)); + } + + [TestCase(0.5f, 50)] + [TestCase(1, 100)] + [TestCase(1.5f, 150)] + public void TestSliderMultiplier(float multiplier, float expectedSpacing) + { + AddStep($"set speed multiplier = {multiplier}", () => editorBeatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier = multiplier); + createGrid(); + AddAssert($"spacing = {expectedSpacing}", () => Precision.AlmostEquals(expectedSpacing, grid.DistanceSpacing)); + } + + [Test] + public void TestCursorInCentre() + { + createGrid(); + + AddStep("move mouse to centre", () => InputManager.MoveMouseTo(grid.ToScreenSpace(grid_position))); + assertSnappedDistance((float)beat_length); + } + + [Test] + public void TestCursorBeforeMovementPoint() + { + createGrid(); + + AddStep("move mouse to just before movement point", () => InputManager.MoveMouseTo(grid.ToScreenSpace(grid_position + new Vector2((float)beat_length, 0) * 1.49f))); + assertSnappedDistance((float)beat_length); + } + + [Test] + public void TestCursorAfterMovementPoint() + { + createGrid(); + + AddStep("move mouse to just after movement point", () => InputManager.MoveMouseTo(grid.ToScreenSpace(grid_position + new Vector2((float)beat_length, 0) * 1.51f))); + assertSnappedDistance((float)beat_length * 2); + } + + private void assertSnappedDistance(float expectedDistance) => AddAssert($"snap distance = {expectedDistance}", () => + { + Vector2 snappedPosition = grid.GetSnapPosition(grid.ToLocalSpace(InputManager.CurrentState.Mouse.Position)); + float distance = Vector2.Distance(snappedPosition, grid_position); + + return Precision.AlmostEquals(expectedDistance, distance); + }); + private void createGrid() { AddStep("create grid", () => @@ -86,28 +155,77 @@ namespace osu.Game.Rulesets.Osu.Tests RelativeSizeAxes = Axes.Both, Colour = Color4.SlateGray }, - grid = new OsuBeatSnapGrid(new HitCircle { Position = grid_position }), - cursor = new Circle - { - Origin = Anchor.Centre, - Size = new Vector2(50), - Colour = Color4.Red - } + grid = new TestOsuBeatSnapGrid(new HitCircle { Position = grid_position }), + new SnappingCursorContainer { GetSnapPosition = v => grid.GetSnapPosition(grid.ToLocalSpace(v)) } }; }); } + private class SnappingCursorContainer : CompositeDrawable + { + public Func GetSnapPosition; + + private readonly Drawable cursor; + + public SnappingCursorContainer() + { + RelativeSizeAxes = Axes.Both; + + InternalChild = cursor = new Circle + { + Origin = Anchor.Centre, + Size = new Vector2(50), + Colour = Color4.Red + }; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + updatePosition(GetContainingInputManager().CurrentState.Mouse.Position); + } + + protected override bool OnMouseMove(MouseMoveEvent e) + { + base.OnMouseMove(e); + + updatePosition(e.ScreenSpaceMousePosition); + return true; + } + + private void updatePosition(Vector2 screenSpacePosition) + { + cursor.Position = GetSnapPosition.Invoke(screenSpacePosition); + } + } + + private class TestOsuBeatSnapGrid : OsuBeatSnapGrid + { + public new float DistanceSpacing => base.DistanceSpacing; + + public TestOsuBeatSnapGrid(OsuHitObject hitObject) + : base(hitObject) + { + } + } + private abstract class CircularBeatSnapGrid : BeatSnapGrid { - protected override void CreateGrid(Vector2 startPosition) + protected CircularBeatSnapGrid(HitObject hitObject, Vector2 centrePosition) + : base(hitObject, centrePosition) + { + } + + protected override void CreateContent(Vector2 centrePosition) { float maxDistance = Math.Max( - Vector2.Distance(startPosition, Vector2.Zero), + Vector2.Distance(centrePosition, Vector2.Zero), Math.Max( - Vector2.Distance(startPosition, new Vector2(DrawWidth, 0)), + Vector2.Distance(centrePosition, new Vector2(DrawWidth, 0)), Math.Max( - Vector2.Distance(startPosition, new Vector2(0, DrawHeight)), - Vector2.Distance(startPosition, DrawSize)))); + Vector2.Distance(centrePosition, new Vector2(0, DrawHeight)), + Vector2.Distance(centrePosition, DrawSize)))); int requiredCircles = (int)(maxDistance / DistanceSpacing); @@ -118,7 +236,7 @@ namespace osu.Game.Rulesets.Osu.Tests AddInternal(new CircularProgress { Origin = Anchor.Centre, - Position = startPosition, + Position = centrePosition, Current = { Value = 1 }, Size = new Vector2(radius), InnerRadius = 4 * 1f / radius, @@ -129,7 +247,7 @@ namespace osu.Game.Rulesets.Osu.Tests public override Vector2 GetSnapPosition(Vector2 position) { - Vector2 direction = position - StartPosition; + Vector2 direction = position - CentrePosition; float distance = direction.Length; float radius = DistanceSpacing; @@ -140,7 +258,7 @@ namespace osu.Game.Rulesets.Osu.Tests Vector2 normalisedDirection = direction * new Vector2(1f / distance); - return StartPosition + normalisedDirection * radialCount * radius; + return CentrePosition + normalisedDirection * radialCount * radius; } } From 45835f97a177597fb236793116382f5b8c7918ea Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 11 Oct 2019 17:13:28 +0900 Subject: [PATCH 03/19] Split out grids into separate files --- .../TestSceneOsuBeatSnapGrid.cs | 80 +------------------ osu.Game.Rulesets.Osu/Edit/OsuBeatSnapGrid.cs | 33 ++++++++ .../Components/CircularBeatSnapGrid.cs | 63 +++++++++++++++ 3 files changed, 97 insertions(+), 79 deletions(-) create mode 100644 osu.Game.Rulesets.Osu/Edit/OsuBeatSnapGrid.cs create mode 100644 osu.Game/Screens/Edit/Compose/Components/CircularBeatSnapGrid.cs diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneOsuBeatSnapGrid.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuBeatSnapGrid.cs index 7baa2f0d72..7399f12372 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneOsuBeatSnapGrid.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuBeatSnapGrid.cs @@ -7,16 +7,13 @@ using osu.Framework.Allocation; 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.Framework.MathUtils; -using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; -using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Osu.Beatmaps; +using osu.Game.Rulesets.Osu.Edit; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Screens.Edit; -using osu.Game.Screens.Edit.Compose.Components; using osu.Game.Tests.Visual; using osuTK; using osuTK.Graphics; @@ -209,80 +206,5 @@ namespace osu.Game.Rulesets.Osu.Tests { } } - - private abstract class CircularBeatSnapGrid : BeatSnapGrid - { - protected CircularBeatSnapGrid(HitObject hitObject, Vector2 centrePosition) - : base(hitObject, centrePosition) - { - } - - protected override void CreateContent(Vector2 centrePosition) - { - float maxDistance = Math.Max( - Vector2.Distance(centrePosition, Vector2.Zero), - Math.Max( - Vector2.Distance(centrePosition, new Vector2(DrawWidth, 0)), - Math.Max( - Vector2.Distance(centrePosition, new Vector2(0, DrawHeight)), - Vector2.Distance(centrePosition, DrawSize)))); - - int requiredCircles = (int)(maxDistance / DistanceSpacing); - - for (int i = 0; i < requiredCircles; i++) - { - float radius = (i + 1) * DistanceSpacing * 2; - - AddInternal(new CircularProgress - { - Origin = Anchor.Centre, - Position = centrePosition, - Current = { Value = 1 }, - Size = new Vector2(radius), - InnerRadius = 4 * 1f / radius, - Colour = GetColourForBeatIndex(i) - }); - } - } - - public override Vector2 GetSnapPosition(Vector2 position) - { - Vector2 direction = position - CentrePosition; - float distance = direction.Length; - - float radius = DistanceSpacing; - int radialCount = Math.Max(1, (int)Math.Round(distance / radius)); - - if (radialCount <= 0) - return position; - - Vector2 normalisedDirection = direction * new Vector2(1f / distance); - - return CentrePosition + normalisedDirection * radialCount * radius; - } - } - - private class OsuBeatSnapGrid : CircularBeatSnapGrid - { - /// - /// Scoring distance with a speed-adjusted beat length of 1 second. - /// - private const float base_scoring_distance = 100; - - public OsuBeatSnapGrid(OsuHitObject hitObject) - : base(hitObject, hitObject.StackedEndPosition) - { - } - - protected override float GetVelocity(double time, ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty) - { - TimingControlPoint timingPoint = controlPointInfo.TimingPointAt(time); - DifficultyControlPoint difficultyPoint = controlPointInfo.DifficultyPointAt(time); - - double scoringDistance = base_scoring_distance * difficulty.SliderMultiplier * difficultyPoint.SpeedMultiplier; - - return (float)(scoringDistance / timingPoint.BeatLength); - } - } } } diff --git a/osu.Game.Rulesets.Osu/Edit/OsuBeatSnapGrid.cs b/osu.Game.Rulesets.Osu/Edit/OsuBeatSnapGrid.cs new file mode 100644 index 0000000000..d453e3d062 --- /dev/null +++ b/osu.Game.Rulesets.Osu/Edit/OsuBeatSnapGrid.cs @@ -0,0 +1,33 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Screens.Edit.Compose.Components; + +namespace osu.Game.Rulesets.Osu.Edit +{ + public class OsuBeatSnapGrid : CircularBeatSnapGrid + { + /// + /// Scoring distance with a speed-adjusted beat length of 1 second. + /// + private const float base_scoring_distance = 100; + + public OsuBeatSnapGrid(OsuHitObject hitObject) + : base(hitObject, hitObject.StackedEndPosition) + { + } + + protected override float GetVelocity(double time, ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty) + { + TimingControlPoint timingPoint = controlPointInfo.TimingPointAt(time); + DifficultyControlPoint difficultyPoint = controlPointInfo.DifficultyPointAt(time); + + double scoringDistance = base_scoring_distance * difficulty.SliderMultiplier * difficultyPoint.SpeedMultiplier; + + return (float)(scoringDistance / timingPoint.BeatLength); + } + } +} diff --git a/osu.Game/Screens/Edit/Compose/Components/CircularBeatSnapGrid.cs b/osu.Game/Screens/Edit/Compose/Components/CircularBeatSnapGrid.cs new file mode 100644 index 0000000000..8492771808 --- /dev/null +++ b/osu.Game/Screens/Edit/Compose/Components/CircularBeatSnapGrid.cs @@ -0,0 +1,63 @@ +// 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.Graphics; +using osu.Framework.Graphics.UserInterface; +using osu.Game.Rulesets.Objects; +using osuTK; + +namespace osu.Game.Screens.Edit.Compose.Components +{ + public abstract class CircularBeatSnapGrid : BeatSnapGrid + { + protected CircularBeatSnapGrid(HitObject hitObject, Vector2 centrePosition) + : base(hitObject, centrePosition) + { + } + + protected override void CreateContent(Vector2 centrePosition) + { + float maxDistance = Math.Max( + Vector2.Distance(centrePosition, Vector2.Zero), + Math.Max( + Vector2.Distance(centrePosition, new Vector2(DrawWidth, 0)), + Math.Max( + Vector2.Distance(centrePosition, new Vector2(0, DrawHeight)), + Vector2.Distance(centrePosition, DrawSize)))); + + int requiredCircles = (int)(maxDistance / DistanceSpacing); + + for (int i = 0; i < requiredCircles; i++) + { + float radius = (i + 1) * DistanceSpacing * 2; + + AddInternal(new CircularProgress + { + Origin = Anchor.Centre, + Position = centrePosition, + Current = { Value = 1 }, + Size = new Vector2(radius), + InnerRadius = 4 * 1f / radius, + Colour = GetColourForBeatIndex(i) + }); + } + } + + public override Vector2 GetSnapPosition(Vector2 position) + { + Vector2 direction = position - CentrePosition; + float distance = direction.Length; + + float radius = DistanceSpacing; + int radialCount = Math.Max(1, (int)Math.Round(distance / radius)); + + if (radialCount <= 0) + return position; + + Vector2 normalisedDirection = direction * new Vector2(1f / distance); + + return CentrePosition + normalisedDirection * radialCount * radius; + } + } +} From 12cd57744b87d559679e179f5f366ab1e64f6238 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 15 Oct 2019 16:14:06 +0900 Subject: [PATCH 04/19] Make RulestStore initialise at construction time --- .../Background/TestSceneUserDimContainer.cs | 6 ++ .../SongSelect/TestScenePlaySongSelect.cs | 6 ++ osu.Game/OsuGameBase.cs | 6 ++ osu.Game/Rulesets/RulesetStore.cs | 57 +++++++++++-------- 4 files changed, 52 insertions(+), 23 deletions(-) diff --git a/osu.Game.Tests/Visual/Background/TestSceneUserDimContainer.cs b/osu.Game.Tests/Visual/Background/TestSceneUserDimContainer.cs index 3061a3a542..f858174ff2 100644 --- a/osu.Game.Tests/Visual/Background/TestSceneUserDimContainer.cs +++ b/osu.Game.Tests/Visual/Background/TestSceneUserDimContainer.cs @@ -285,6 +285,12 @@ namespace osu.Game.Tests.Visual.Background }); } + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + rulesets?.Dispose(); + } + private class DummySongSelect : PlaySongSelect { protected override BackgroundScreen CreateBackground() diff --git a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs index a7020b6534..efe7fee5e4 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs @@ -349,5 +349,11 @@ namespace osu.Game.Tests.Visual.SongSelect DateAdded = DateTimeOffset.UtcNow, }; } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + rulesets?.Dispose(); + } } } diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 8578517a17..194a439b06 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -298,6 +298,12 @@ namespace osu.Game public string[] HandledExtensions => fileImporters.SelectMany(i => i.HandledExtensions).ToArray(); + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + RulesetStore?.Dispose(); + } + private class OsuUserInputManager : UserInputManager { protected override MouseButtonEventManager CreateButtonManagerFor(MouseButton button) diff --git a/osu.Game/Rulesets/RulesetStore.cs b/osu.Game/Rulesets/RulesetStore.cs index 47aad43966..1df8568ee1 100644 --- a/osu.Game/Rulesets/RulesetStore.cs +++ b/osu.Game/Rulesets/RulesetStore.cs @@ -14,25 +14,22 @@ namespace osu.Game.Rulesets /// /// Todo: All of this needs to be moved to a RulesetStore. /// - public class RulesetStore : DatabaseBackedStore + public class RulesetStore : DatabaseBackedStore, IDisposable { - private static readonly Dictionary loaded_assemblies = new Dictionary(); + private const string ruleset_library_prefix = "osu.Game.Rulesets"; - static RulesetStore() - { - AppDomain.CurrentDomain.AssemblyResolve += currentDomain_AssemblyResolve; - - // On android in release configuration assemblies are loaded from the apk directly into memory. - // We cannot read assemblies from cwd, so should check loaded assemblies instead. - loadFromAppDomain(); - - loadFromDisk(); - } + private readonly Dictionary loadedAssemblies = new Dictionary(); public RulesetStore(IDatabaseContextFactory factory) : base(factory) { + // On android in release configuration assemblies are loaded from the apk directly into memory. + // We cannot read assemblies from cwd, so should check loaded assemblies instead. + loadFromAppDomain(); + loadFromDisk(); addMissingRulesets(); + + AppDomain.CurrentDomain.AssemblyResolve += resolveRulesetAssembly; } /// @@ -54,9 +51,7 @@ namespace osu.Game.Rulesets /// public IEnumerable AvailableRulesets { get; private set; } - private static Assembly currentDomain_AssemblyResolve(object sender, ResolveEventArgs args) => loaded_assemblies.Keys.FirstOrDefault(a => a.FullName == args.Name); - - private const string ruleset_library_prefix = "osu.Game.Rulesets"; + private Assembly resolveRulesetAssembly(object sender, ResolveEventArgs args) => loadedAssemblies.Keys.FirstOrDefault(a => a.FullName == args.Name); private void addMissingRulesets() { @@ -64,7 +59,7 @@ namespace osu.Game.Rulesets { var context = usage.Context; - var instances = loaded_assemblies.Values.Select(r => (Ruleset)Activator.CreateInstance(r, (RulesetInfo)null)).ToList(); + var instances = loadedAssemblies.Values.Select(r => (Ruleset)Activator.CreateInstance(r, (RulesetInfo)null)).ToList(); //add all legacy modes in correct order foreach (var r in instances.Where(r => r.LegacyID != null).OrderBy(r => r.LegacyID)) @@ -113,7 +108,7 @@ namespace osu.Game.Rulesets } } - private static void loadFromAppDomain() + private void loadFromAppDomain() { foreach (var ruleset in AppDomain.CurrentDomain.GetAssemblies()) { @@ -126,7 +121,7 @@ namespace osu.Game.Rulesets } } - private static void loadFromDisk() + private void loadFromDisk() { try { @@ -141,11 +136,11 @@ namespace osu.Game.Rulesets } } - private static void loadRulesetFromFile(string file) + private void loadRulesetFromFile(string file) { var filename = Path.GetFileNameWithoutExtension(file); - if (loaded_assemblies.Values.Any(t => t.Namespace == filename)) + if (loadedAssemblies.Values.Any(t => t.Namespace == filename)) return; try @@ -158,19 +153,35 @@ namespace osu.Game.Rulesets } } - private static void addRuleset(Assembly assembly) + private void addRuleset(Assembly assembly) { - if (loaded_assemblies.ContainsKey(assembly)) + if (loadedAssemblies.ContainsKey(assembly)) return; try { - loaded_assemblies[assembly] = assembly.GetTypes().First(t => t.IsPublic && t.IsSubclassOf(typeof(Ruleset))); + loadedAssemblies[assembly] = assembly.GetTypes().First(t => t.IsPublic && t.IsSubclassOf(typeof(Ruleset))); } catch (Exception e) { Logger.Error(e, $"Failed to add ruleset {assembly}"); } } + + ~RulesetStore() + { + Dispose(false); + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + protected virtual void Dispose(bool disposing) + { + AppDomain.CurrentDomain.AssemblyResolve -= resolveRulesetAssembly; + } } } From 96c6aeefe92a864ecd783f253506b899a6a18741 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 15 Oct 2019 16:16:33 +0900 Subject: [PATCH 05/19] Remove out-of-date todo --- osu.Game/Rulesets/RulesetStore.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/osu.Game/Rulesets/RulesetStore.cs b/osu.Game/Rulesets/RulesetStore.cs index 1df8568ee1..0e6e0b8676 100644 --- a/osu.Game/Rulesets/RulesetStore.cs +++ b/osu.Game/Rulesets/RulesetStore.cs @@ -11,9 +11,6 @@ using osu.Game.Database; namespace osu.Game.Rulesets { - /// - /// Todo: All of this needs to be moved to a RulesetStore. - /// public class RulesetStore : DatabaseBackedStore, IDisposable { private const string ruleset_library_prefix = "osu.Game.Rulesets"; From 14c72f85fa56387b3932908c616c0de68e3aee36 Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Tue, 15 Oct 2019 23:40:48 +0300 Subject: [PATCH 06/19] Fix incorrect beatmap set info equality check on non-online set info --- osu.Game/Beatmaps/BeatmapSetInfo.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/BeatmapSetInfo.cs b/osu.Game/Beatmaps/BeatmapSetInfo.cs index 03bc7c7312..90346a8c8b 100644 --- a/osu.Game/Beatmaps/BeatmapSetInfo.cs +++ b/osu.Game/Beatmaps/BeatmapSetInfo.cs @@ -63,6 +63,12 @@ namespace osu.Game.Beatmaps public bool Protected { get; set; } - public bool Equals(BeatmapSetInfo other) => OnlineBeatmapSetID == other?.OnlineBeatmapSetID; + public bool Equals(BeatmapSetInfo other) + { + if (!OnlineBeatmapSetID.HasValue || !(other?.OnlineBeatmapSetID.HasValue ?? false)) + return ReferenceEquals(this, other); + + return OnlineBeatmapSetID == other.OnlineBeatmapSetID; + } } } From 5ac5e34f8585fc0910ef4d6cf81307ca90e51b47 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 16 Oct 2019 19:32:45 +0900 Subject: [PATCH 07/19] Use a cleaner distance function --- .../Edit/Compose/Components/CircularBeatSnapGrid.cs | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/CircularBeatSnapGrid.cs b/osu.Game/Screens/Edit/Compose/Components/CircularBeatSnapGrid.cs index 8492771808..bf363aeb37 100644 --- a/osu.Game/Screens/Edit/Compose/Components/CircularBeatSnapGrid.cs +++ b/osu.Game/Screens/Edit/Compose/Components/CircularBeatSnapGrid.cs @@ -18,13 +18,9 @@ namespace osu.Game.Screens.Edit.Compose.Components protected override void CreateContent(Vector2 centrePosition) { - float maxDistance = Math.Max( - Vector2.Distance(centrePosition, Vector2.Zero), - Math.Max( - Vector2.Distance(centrePosition, new Vector2(DrawWidth, 0)), - Math.Max( - Vector2.Distance(centrePosition, new Vector2(0, DrawHeight)), - Vector2.Distance(centrePosition, DrawSize)))); + float dx = Math.Max(centrePosition.X, DrawWidth - centrePosition.X); + float dy = Math.Max(centrePosition.Y, DrawHeight - centrePosition.Y); + float maxDistance = new Vector2(dx, dy).Length; int requiredCircles = (int)(maxDistance / DistanceSpacing); From 2d4b7dc361ca061fd74cef1654ab86875c58d518 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 16 Oct 2019 19:33:18 +0900 Subject: [PATCH 08/19] Remove redundant code --- .../Screens/Edit/Compose/Components/CircularBeatSnapGrid.cs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/CircularBeatSnapGrid.cs b/osu.Game/Screens/Edit/Compose/Components/CircularBeatSnapGrid.cs index bf363aeb37..09679f0553 100644 --- a/osu.Game/Screens/Edit/Compose/Components/CircularBeatSnapGrid.cs +++ b/osu.Game/Screens/Edit/Compose/Components/CircularBeatSnapGrid.cs @@ -48,11 +48,7 @@ namespace osu.Game.Screens.Edit.Compose.Components float radius = DistanceSpacing; int radialCount = Math.Max(1, (int)Math.Round(distance / radius)); - if (radialCount <= 0) - return position; - Vector2 normalisedDirection = direction * new Vector2(1f / distance); - return CentrePosition + normalisedDirection * radialCount * radius; } } From b6b8098b989383b0402e2efe48476048f9b05f3f Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 16 Oct 2019 19:44:53 +0900 Subject: [PATCH 09/19] Add an arbitrary offset to prevent div-by-0 --- .../Screens/Edit/Compose/Components/CircularBeatSnapGrid.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game/Screens/Edit/Compose/Components/CircularBeatSnapGrid.cs b/osu.Game/Screens/Edit/Compose/Components/CircularBeatSnapGrid.cs index 09679f0553..5e378f8393 100644 --- a/osu.Game/Screens/Edit/Compose/Components/CircularBeatSnapGrid.cs +++ b/osu.Game/Screens/Edit/Compose/Components/CircularBeatSnapGrid.cs @@ -43,6 +43,10 @@ namespace osu.Game.Screens.Edit.Compose.Components public override Vector2 GetSnapPosition(Vector2 position) { Vector2 direction = position - CentrePosition; + + if (direction == Vector2.Zero) + direction = new Vector2(0.001f, 0.001f); + float distance = direction.Length; float radius = DistanceSpacing; From 79b2c7b480f34c2d122f69aed9a69ae413bf7108 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 16 Oct 2019 20:04:15 +0900 Subject: [PATCH 10/19] Make BeginPlacement() set the hitobject start time --- .../Edit/Blueprints/ManiaPlacementBlueprint.cs | 4 +--- .../Edit/Blueprints/HitCircles/HitCirclePlacementBlueprint.cs | 1 - .../Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs | 2 -- .../Edit/Blueprints/Spinners/SpinnerPlacementBlueprint.cs | 2 -- osu.Game/Rulesets/Edit/PlacementBlueprint.cs | 4 +++- 5 files changed, 4 insertions(+), 9 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaPlacementBlueprint.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaPlacementBlueprint.cs index 3142f22fcd..b28d8bb0e6 100644 --- a/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaPlacementBlueprint.cs @@ -49,10 +49,8 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints if (Column == null) return base.OnMouseDown(e); - HitObject.StartTime = TimeAt(e.ScreenSpaceMousePosition); HitObject.Column = Column.Index; - - BeginPlacement(); + BeginPlacement(TimeAt(e.ScreenSpaceMousePosition)); return true; } diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/HitCircles/HitCirclePlacementBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/HitCircles/HitCirclePlacementBlueprint.cs index 6c08990ad6..bb47c7e464 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/HitCircles/HitCirclePlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/HitCircles/HitCirclePlacementBlueprint.cs @@ -30,7 +30,6 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles protected override bool OnClick(ClickEvent e) { - HitObject.StartTime = EditorClock.CurrentTime; EndPlacement(); return true; } diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs index fc074ef8af..2fb18bf8ba 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs @@ -104,8 +104,6 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders private void beginCurve() { BeginPlacement(); - - HitObject.StartTime = EditorClock.CurrentTime; setState(PlacementState.Body); } diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Spinners/SpinnerPlacementBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Spinners/SpinnerPlacementBlueprint.cs index 8319f49cbc..5525b8936e 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Spinners/SpinnerPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Spinners/SpinnerPlacementBlueprint.cs @@ -41,8 +41,6 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Spinners } else { - HitObject.StartTime = EditorClock.CurrentTime; - isPlacingEnd = true; piece.FadeTo(1f, 150, Easing.OutQuint); diff --git a/osu.Game/Rulesets/Edit/PlacementBlueprint.cs b/osu.Game/Rulesets/Edit/PlacementBlueprint.cs index 290fd8d27d..07283d2245 100644 --- a/osu.Game/Rulesets/Edit/PlacementBlueprint.cs +++ b/osu.Game/Rulesets/Edit/PlacementBlueprint.cs @@ -91,8 +91,10 @@ namespace osu.Game.Rulesets.Edit /// /// Signals that the placement of has started. /// - protected void BeginPlacement() + /// The start time of at the placement point. If null, the current clock time is used. + protected void BeginPlacement(double? startTime = null) { + HitObject.StartTime = startTime ?? EditorClock.CurrentTime; placementHandler.BeginPlacement(HitObject); PlacementBegun = true; } From 405ab07800b204b9cf3b882dfc978378eee1f72c Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Thu, 17 Oct 2019 01:18:29 +0300 Subject: [PATCH 11/19] Check equality by ID -> OnlineBeatmapSetID -> Hash -> ReferenceEquals --- osu.Game/Beatmaps/BeatmapSetInfo.cs | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapSetInfo.cs b/osu.Game/Beatmaps/BeatmapSetInfo.cs index 90346a8c8b..a8b83dca38 100644 --- a/osu.Game/Beatmaps/BeatmapSetInfo.cs +++ b/osu.Game/Beatmaps/BeatmapSetInfo.cs @@ -65,10 +65,19 @@ namespace osu.Game.Beatmaps public bool Equals(BeatmapSetInfo other) { - if (!OnlineBeatmapSetID.HasValue || !(other?.OnlineBeatmapSetID.HasValue ?? false)) - return ReferenceEquals(this, other); + if (other == null) + return false; - return OnlineBeatmapSetID == other.OnlineBeatmapSetID; + if (ID != 0 && other.ID != 0) + return ID == other.ID; + + if (OnlineBeatmapSetID.HasValue && other.OnlineBeatmapSetID.HasValue) + return OnlineBeatmapSetID == other.OnlineBeatmapSetID; + + if (!string.IsNullOrEmpty(Hash) && !string.IsNullOrEmpty(other.Hash)) + return Hash == other.Hash; + + return ReferenceEquals(this, other); } } } From 40fc655b50244d0b0d588abb1b97f3ae9cbde831 Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Thu, 17 Oct 2019 01:19:50 +0300 Subject: [PATCH 12/19] Add equality check test to ensure correct values --- .../NonVisual/BeatmapSetInfoEqualityTest.cs | 48 +++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 osu.Game.Tests/NonVisual/BeatmapSetInfoEqualityTest.cs diff --git a/osu.Game.Tests/NonVisual/BeatmapSetInfoEqualityTest.cs b/osu.Game.Tests/NonVisual/BeatmapSetInfoEqualityTest.cs new file mode 100644 index 0000000000..42a3b4cf43 --- /dev/null +++ b/osu.Game.Tests/NonVisual/BeatmapSetInfoEqualityTest.cs @@ -0,0 +1,48 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using NUnit.Framework; +using osu.Game.Beatmaps; + +namespace osu.Game.Tests.NonVisual +{ + [TestFixture] + public class BeatmapSetInfoEqualityTest + { + [Test] + public void TestOnlineWithOnline() + { + var ourInfo = new BeatmapSetInfo { OnlineBeatmapSetID = 123 }; + var otherInfo = new BeatmapSetInfo { OnlineBeatmapSetID = 123 }; + + Assert.AreEqual(ourInfo, otherInfo); + } + + [Test] + public void TestDatabasedWithDatabased() + { + var ourInfo = new BeatmapSetInfo { ID = 123 }; + var otherInfo = new BeatmapSetInfo { ID = 123 }; + + Assert.AreEqual(ourInfo, otherInfo); + } + + [Test] + public void TestDatabasedWithOnline() + { + var ourInfo = new BeatmapSetInfo { ID = 123, OnlineBeatmapSetID = 12 }; + var otherInfo = new BeatmapSetInfo { OnlineBeatmapSetID = 12 }; + + Assert.AreEqual(ourInfo, otherInfo); + } + + [Test] + public void TestCheckNullID() + { + var ourInfo = new BeatmapSetInfo { Status = BeatmapSetOnlineStatus.Loved }; + var otherInfo = new BeatmapSetInfo { Status = BeatmapSetOnlineStatus.Approved }; + + Assert.AreNotEqual(ourInfo, otherInfo); + } + } +} From 9b9138253c13ba60fbb16f1735b1749722e7324a Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 17 Oct 2019 11:27:23 +0900 Subject: [PATCH 13/19] Remove finalizer --- osu.Game/Rulesets/RulesetStore.cs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/osu.Game/Rulesets/RulesetStore.cs b/osu.Game/Rulesets/RulesetStore.cs index 0e6e0b8676..23988ff0ff 100644 --- a/osu.Game/Rulesets/RulesetStore.cs +++ b/osu.Game/Rulesets/RulesetStore.cs @@ -165,11 +165,6 @@ namespace osu.Game.Rulesets } } - ~RulesetStore() - { - Dispose(false); - } - public void Dispose() { Dispose(true); From f92331531c6e183a4644a423d20b6e9df17df2cc Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 17 Oct 2019 15:32:02 +0900 Subject: [PATCH 14/19] Rename grid to DistanceSnap to be more in line with its purpose --- ...SnapGrid.cs => TestSceneOsuDistanceSnapGrid.cs} | 12 ++++++------ .../{OsuBeatSnapGrid.cs => OsuDistanceSnapGrid.cs} | 4 ++-- ...eatSnapGrid.cs => TestSceneDistanceSnapGrid.cs} | 14 +++++++------- ...BeatSnapGrid.cs => CircularDistanceSnapGrid.cs} | 4 ++-- .../{BeatSnapGrid.cs => DistanceSnapGrid.cs} | 13 ++++++++----- 5 files changed, 25 insertions(+), 22 deletions(-) rename osu.Game.Rulesets.Osu.Tests/{TestSceneOsuBeatSnapGrid.cs => TestSceneOsuDistanceSnapGrid.cs} (94%) rename osu.Game.Rulesets.Osu/Edit/{OsuBeatSnapGrid.cs => OsuDistanceSnapGrid.cs} (90%) rename osu.Game.Tests/Visual/Editor/{TestSceneBeatSnapGrid.cs => TestSceneDistanceSnapGrid.cs} (93%) rename osu.Game/Screens/Edit/Compose/Components/{CircularBeatSnapGrid.cs => CircularDistanceSnapGrid.cs} (92%) rename osu.Game/Screens/Edit/Compose/Components/{BeatSnapGrid.cs => DistanceSnapGrid.cs} (92%) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneOsuBeatSnapGrid.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuDistanceSnapGrid.cs similarity index 94% rename from osu.Game.Rulesets.Osu.Tests/TestSceneOsuBeatSnapGrid.cs rename to osu.Game.Rulesets.Osu.Tests/TestSceneOsuDistanceSnapGrid.cs index 7399f12372..da7708081b 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneOsuBeatSnapGrid.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuDistanceSnapGrid.cs @@ -20,7 +20,7 @@ using osuTK.Graphics; namespace osu.Game.Rulesets.Osu.Tests { - public class TestSceneOsuBeatSnapGrid : ManualInputManagerTestScene + public class TestSceneOsuDistanceSnapGrid : ManualInputManagerTestScene { private const double beat_length = 100; private static readonly Vector2 grid_position = new Vector2(512, 384); @@ -31,9 +31,9 @@ namespace osu.Game.Rulesets.Osu.Tests [Cached] private readonly BindableBeatDivisor beatDivisor = new BindableBeatDivisor(); - private TestOsuBeatSnapGrid grid; + private TestOsuDistanceSnapGrid grid; - public TestSceneOsuBeatSnapGrid() + public TestSceneOsuDistanceSnapGrid() { editorBeatmap = new EditorBeatmap(new OsuBeatmap()); @@ -152,7 +152,7 @@ namespace osu.Game.Rulesets.Osu.Tests RelativeSizeAxes = Axes.Both, Colour = Color4.SlateGray }, - grid = new TestOsuBeatSnapGrid(new HitCircle { Position = grid_position }), + grid = new TestOsuDistanceSnapGrid(new HitCircle { Position = grid_position }), new SnappingCursorContainer { GetSnapPosition = v => grid.GetSnapPosition(grid.ToLocalSpace(v)) } }; }); @@ -197,11 +197,11 @@ namespace osu.Game.Rulesets.Osu.Tests } } - private class TestOsuBeatSnapGrid : OsuBeatSnapGrid + private class TestOsuDistanceSnapGrid : OsuDistanceSnapGrid { public new float DistanceSpacing => base.DistanceSpacing; - public TestOsuBeatSnapGrid(OsuHitObject hitObject) + public TestOsuDistanceSnapGrid(OsuHitObject hitObject) : base(hitObject) { } diff --git a/osu.Game.Rulesets.Osu/Edit/OsuBeatSnapGrid.cs b/osu.Game.Rulesets.Osu/Edit/OsuDistanceSnapGrid.cs similarity index 90% rename from osu.Game.Rulesets.Osu/Edit/OsuBeatSnapGrid.cs rename to osu.Game.Rulesets.Osu/Edit/OsuDistanceSnapGrid.cs index d453e3d062..558993f8b2 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuBeatSnapGrid.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuDistanceSnapGrid.cs @@ -8,14 +8,14 @@ using osu.Game.Screens.Edit.Compose.Components; namespace osu.Game.Rulesets.Osu.Edit { - public class OsuBeatSnapGrid : CircularBeatSnapGrid + public class OsuDistanceSnapGrid : CircularDistanceSnapGrid { /// /// Scoring distance with a speed-adjusted beat length of 1 second. /// private const float base_scoring_distance = 100; - public OsuBeatSnapGrid(OsuHitObject hitObject) + public OsuDistanceSnapGrid(OsuHitObject hitObject) : base(hitObject, hitObject.StackedEndPosition) { } diff --git a/osu.Game.Tests/Visual/Editor/TestSceneBeatSnapGrid.cs b/osu.Game.Tests/Visual/Editor/TestSceneDistanceSnapGrid.cs similarity index 93% rename from osu.Game.Tests/Visual/Editor/TestSceneBeatSnapGrid.cs rename to osu.Game.Tests/Visual/Editor/TestSceneDistanceSnapGrid.cs index 073cec7315..a9e5930478 100644 --- a/osu.Game.Tests/Visual/Editor/TestSceneBeatSnapGrid.cs +++ b/osu.Game.Tests/Visual/Editor/TestSceneDistanceSnapGrid.cs @@ -19,7 +19,7 @@ using osuTK.Graphics; namespace osu.Game.Tests.Visual.Editor { - public class TestSceneBeatSnapGrid : EditorClockTestScene + public class TestSceneDistanceSnapGrid : EditorClockTestScene { private const double beat_length = 100; private static readonly Vector2 grid_position = new Vector2(512, 384); @@ -27,9 +27,9 @@ namespace osu.Game.Tests.Visual.Editor [Cached(typeof(IEditorBeatmap))] private readonly EditorBeatmap editorBeatmap; - private TestBeatSnapGrid grid; + private TestDistanceSnapGrid grid; - public TestSceneBeatSnapGrid() + public TestSceneDistanceSnapGrid() { editorBeatmap = new EditorBeatmap(new OsuBeatmap()); editorBeatmap.ControlPointInfo.TimingPoints.Add(new TimingControlPoint { BeatLength = beat_length }); @@ -112,7 +112,7 @@ namespace osu.Game.Tests.Visual.Editor AddAssert("snap time is now 0.5 beats away", () => Precision.AlmostEquals(beat_length / 2, grid.GetSnapTime(snapPosition), 0.01)); } - private void createGrid(Action func = null, string description = null) + private void createGrid(Action func = null, string description = null) { AddStep($"create grid {description ?? string.Empty}", () => { @@ -123,20 +123,20 @@ namespace osu.Game.Tests.Visual.Editor RelativeSizeAxes = Axes.Both, Colour = Color4.SlateGray }, - grid = new TestBeatSnapGrid(new HitObject(), grid_position) + grid = new TestDistanceSnapGrid(new HitObject(), grid_position) }; func?.Invoke(grid); }); } - private class TestBeatSnapGrid : BeatSnapGrid + private class TestDistanceSnapGrid : DistanceSnapGrid { public new float Velocity = 1; public new float DistanceSpacing => base.DistanceSpacing; - public TestBeatSnapGrid(HitObject hitObject, Vector2 centrePosition) + public TestDistanceSnapGrid(HitObject hitObject, Vector2 centrePosition) : base(hitObject, centrePosition) { } diff --git a/osu.Game/Screens/Edit/Compose/Components/CircularBeatSnapGrid.cs b/osu.Game/Screens/Edit/Compose/Components/CircularDistanceSnapGrid.cs similarity index 92% rename from osu.Game/Screens/Edit/Compose/Components/CircularBeatSnapGrid.cs rename to osu.Game/Screens/Edit/Compose/Components/CircularDistanceSnapGrid.cs index 5e378f8393..3cbf926d4f 100644 --- a/osu.Game/Screens/Edit/Compose/Components/CircularBeatSnapGrid.cs +++ b/osu.Game/Screens/Edit/Compose/Components/CircularDistanceSnapGrid.cs @@ -9,9 +9,9 @@ using osuTK; namespace osu.Game.Screens.Edit.Compose.Components { - public abstract class CircularBeatSnapGrid : BeatSnapGrid + public abstract class CircularDistanceSnapGrid : DistanceSnapGrid { - protected CircularBeatSnapGrid(HitObject hitObject, Vector2 centrePosition) + protected CircularDistanceSnapGrid(HitObject hitObject, Vector2 centrePosition) : base(hitObject, centrePosition) { } diff --git a/osu.Game/Screens/Edit/Compose/Components/BeatSnapGrid.cs b/osu.Game/Screens/Edit/Compose/Components/DistanceSnapGrid.cs similarity index 92% rename from osu.Game/Screens/Edit/Compose/Components/BeatSnapGrid.cs rename to osu.Game/Screens/Edit/Compose/Components/DistanceSnapGrid.cs index 9040843144..299e78b7c0 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BeatSnapGrid.cs +++ b/osu.Game/Screens/Edit/Compose/Components/DistanceSnapGrid.cs @@ -15,7 +15,10 @@ using osuTK; namespace osu.Game.Screens.Edit.Compose.Components { - public abstract class BeatSnapGrid : CompositeDrawable + /// + /// A grid which takes user input and returns a quantized ("snapped") position and time. + /// + public abstract class DistanceSnapGrid : CompositeDrawable { /// /// The velocity of the beatmap at the point of placement in pixels per millisecond. @@ -48,7 +51,7 @@ namespace osu.Game.Screens.Edit.Compose.Components private double startTime; private double beatLength; - protected BeatSnapGrid(HitObject hitObject, Vector2 centrePosition) + protected DistanceSnapGrid(HitObject hitObject, Vector2 centrePosition) { this.hitObject = hitObject; this.CentrePosition = centrePosition; @@ -114,14 +117,14 @@ namespace osu.Game.Screens.Edit.Compose.Components /// /// Snaps a position to this grid. /// - /// The original position in coordinate space local to this . - /// The snapped position in coordinate space local to this . + /// The original position in coordinate space local to this . + /// The snapped position in coordinate space local to this . public abstract Vector2 GetSnapPosition(Vector2 position); /// /// Retrieves the time at a snapped position. /// - /// The snapped position in coordinate space local to this . + /// The snapped position in coordinate space local to this . /// The time at the snapped position. public double GetSnapTime(Vector2 position) => startTime + (position - CentrePosition).Length / Velocity; From f3ed71d3361bd1eb81e23432e457f93c54caa3db Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 17 Oct 2019 16:36:47 +0900 Subject: [PATCH 15/19] Move scoring distance constant to a central/shared location --- osu.Game.Rulesets.Osu/Edit/OsuDistanceSnapGrid.cs | 7 +------ osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs | 8 ++++++++ osu.Game.Rulesets.Osu/Objects/Slider.cs | 7 +------ 3 files changed, 10 insertions(+), 12 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuDistanceSnapGrid.cs b/osu.Game.Rulesets.Osu/Edit/OsuDistanceSnapGrid.cs index 558993f8b2..f701712739 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuDistanceSnapGrid.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuDistanceSnapGrid.cs @@ -10,11 +10,6 @@ namespace osu.Game.Rulesets.Osu.Edit { public class OsuDistanceSnapGrid : CircularDistanceSnapGrid { - /// - /// Scoring distance with a speed-adjusted beat length of 1 second. - /// - private const float base_scoring_distance = 100; - public OsuDistanceSnapGrid(OsuHitObject hitObject) : base(hitObject, hitObject.StackedEndPosition) { @@ -25,7 +20,7 @@ namespace osu.Game.Rulesets.Osu.Edit TimingControlPoint timingPoint = controlPointInfo.TimingPointAt(time); DifficultyControlPoint difficultyPoint = controlPointInfo.DifficultyPointAt(time); - double scoringDistance = base_scoring_distance * difficulty.SliderMultiplier * difficultyPoint.SpeedMultiplier; + double scoringDistance = OsuHitObject.BASE_SCORING_DISTANCE * difficulty.SliderMultiplier * difficultyPoint.SpeedMultiplier; return (float)(scoringDistance / timingPoint.BeatLength); } diff --git a/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs b/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs index 80e013fe68..b506c1f918 100644 --- a/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs +++ b/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs @@ -14,8 +14,16 @@ namespace osu.Game.Rulesets.Osu.Objects { public abstract class OsuHitObject : HitObject, IHasComboInformation, IHasPosition { + /// + /// The radius of hit objects (ie. the radius of a ). + /// public const float OBJECT_RADIUS = 64; + /// + /// Scoring distance with a speed-adjusted beat length of 1 second (ie. the speed slider balls move through their track). + /// + internal const float BASE_SCORING_DISTANCE = 100; + public double TimePreempt = 600; public double TimeFadeIn = 400; diff --git a/osu.Game.Rulesets.Osu/Objects/Slider.cs b/osu.Game.Rulesets.Osu/Objects/Slider.cs index 9bed123465..d98d72331a 100644 --- a/osu.Game.Rulesets.Osu/Objects/Slider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Slider.cs @@ -19,11 +19,6 @@ namespace osu.Game.Rulesets.Osu.Objects { public class Slider : OsuHitObject, IHasCurve { - /// - /// Scoring distance with a speed-adjusted beat length of 1 second. - /// - private const float base_scoring_distance = 100; - public double EndTime => StartTime + this.SpanCount() * Path.Distance / Velocity; public double Duration => EndTime - StartTime; @@ -123,7 +118,7 @@ namespace osu.Game.Rulesets.Osu.Objects TimingControlPoint timingPoint = controlPointInfo.TimingPointAt(StartTime); DifficultyControlPoint difficultyPoint = controlPointInfo.DifficultyPointAt(StartTime); - double scoringDistance = base_scoring_distance * difficulty.SliderMultiplier * difficultyPoint.SpeedMultiplier; + double scoringDistance = BASE_SCORING_DISTANCE * difficulty.SliderMultiplier * difficultyPoint.SpeedMultiplier; Velocity = scoringDistance / timingPoint.BeatLength; TickDistance = scoringDistance / difficulty.SliderTickRate * TickDistanceMultiplier; From c4cc960e1564944a18964c606b2b64b8f4d927de Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 17 Oct 2019 18:00:15 +0900 Subject: [PATCH 16/19] Fix mania hitobject selections not moving correctly --- .../Edit/ManiaSelectionHandler.cs | 23 +++++++++++-------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.cs b/osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.cs index f576c43e52..2fba0639da 100644 --- a/osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.cs +++ b/osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.cs @@ -65,24 +65,27 @@ namespace osu.Game.Rulesets.Mania.Edit private void performDragMovement(MoveSelectionEvent moveEvent) { + float delta = moveEvent.InstantDelta.Y; + + // When scrolling downwards the anchor position is at the bottom of the screen, however the movement event assumes the anchor is at the top of the screen. + // This causes the delta to assume a positive hitobject position, and which can be corrected for by subtracting the parent height. + if (scrollingInfo.Direction.Value == ScrollingDirection.Down) + delta -= moveEvent.Blueprint.HitObject.Parent.DrawHeight; + foreach (var b in SelectedBlueprints) { var hitObject = b.HitObject; - var objectParent = (HitObjectContainer)hitObject.Parent; - // Using the hitobject position is required since AdjustPosition can be invoked multiple times per frame - // without the position having been updated by the parenting ScrollingHitObjectContainer - hitObject.Y += moveEvent.InstantDelta.Y; + // StartTime could be used to adjust the position if only one movement event was received per frame. + // However this is not the case and ScrollingHitObjectContainer performs movement in UpdateAfterChildren() so the position must also be updated to be valid for further movement events + hitObject.Y += delta; - float targetPosition; + float targetPosition = hitObject.Position.Y; - // If we're scrolling downwards, a position of 0 is actually further away from the hit target - // so we need to flip the vertical coordinate in the hitobject container's space + // The scrolling algorithm always assumes an anchor at the top of the screen, so the position must be flipped when scrolling downwards to reflect a top anchor if (scrollingInfo.Direction.Value == ScrollingDirection.Down) - targetPosition = -hitObject.Position.Y; - else - targetPosition = hitObject.Position.Y; + targetPosition = -targetPosition; objectParent.Remove(hitObject); From 6b0976ff1e2e1d13747f0e6a06114ff52b056b26 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 15 Oct 2019 15:07:06 +0900 Subject: [PATCH 17/19] Remove a weird unicode charcter from file --- osu.Game/Overlays/AccountCreation/ScreenWarning.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/AccountCreation/ScreenWarning.cs b/osu.Game/Overlays/AccountCreation/ScreenWarning.cs index be417f4aac..f91d2e3323 100644 --- a/osu.Game/Overlays/AccountCreation/ScreenWarning.cs +++ b/osu.Game/Overlays/AccountCreation/ScreenWarning.cs @@ -121,7 +121,7 @@ namespace osu.Game.Overlays.AccountCreation multiAccountExplanationText.AddText("? osu! has a policy of "); multiAccountExplanationText.AddText("one account per person!", cp => cp.Colour = colours.Yellow); multiAccountExplanationText.AddText(" Please be aware that creating more than one account per person may result in "); - multiAccountExplanationText.AddText("permanent deactivation of accounts", cp => cp.Colour = colours.Yellow); + multiAccountExplanationText.AddText("permanent deactivation of accounts", cp => cp.Colour = colours.Yellow); multiAccountExplanationText.AddText("."); furtherAssistance.AddText("Need further assistance? Contact us via our "); From 89f50b26f72f56f932df01a99f94f93de621da8e Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 18 Oct 2019 17:32:11 +0900 Subject: [PATCH 18/19] Fix hitobject combo colour potentially not getting adjusted --- .../TestSceneHitObjectAccentColour.cs | 143 ++++++++++++++++++ .../Objects/Drawables/DrawableHitObject.cs | 2 +- 2 files changed, 144 insertions(+), 1 deletion(-) create mode 100644 osu.Game.Tests/Gameplay/TestSceneHitObjectAccentColour.cs diff --git a/osu.Game.Tests/Gameplay/TestSceneHitObjectAccentColour.cs b/osu.Game.Tests/Gameplay/TestSceneHitObjectAccentColour.cs new file mode 100644 index 0000000000..6d7159a825 --- /dev/null +++ b/osu.Game.Tests/Gameplay/TestSceneHitObjectAccentColour.cs @@ -0,0 +1,143 @@ +// 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 NUnit.Framework; +using osu.Framework.Audio.Sample; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Textures; +using osu.Framework.Testing; +using osu.Game.Audio; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Objects.Types; +using osu.Game.Skinning; +using osu.Game.Tests.Visual; +using osuTK.Graphics; + +namespace osu.Game.Tests.Gameplay +{ + [HeadlessTest] + public class TestSceneHitObjectAccentColour : OsuTestScene + { + private Container skinContainer; + + [SetUp] + public void Setup() => Schedule(() => Child = skinContainer = new SkinProvidingContainer(new TestSkin())); + + [Test] + public void TestChangeComboIndexBeforeLoad() + { + TestDrawableHitObject hitObject = null; + + AddStep("set combo and add hitobject", () => + { + hitObject = new TestDrawableHitObject(); + hitObject.HitObject.ComboIndex = 1; + + skinContainer.Add(hitObject); + }); + + AddAssert("combo colour is green", () => hitObject.AccentColour.Value == Color4.Green); + } + + [Test] + public void TestChangeComboIndexDuringLoad() + { + TestDrawableHitObject hitObject = null; + + AddStep("add hitobject and set combo", () => + { + skinContainer.Add(hitObject = new TestDrawableHitObject()); + hitObject.HitObject.ComboIndex = 1; + }); + + AddAssert("combo colour is green", () => hitObject.AccentColour.Value == Color4.Green); + } + + [Test] + public void TestChangeComboIndexAfterLoad() + { + TestDrawableHitObject hitObject = null; + + AddStep("add hitobject", () => skinContainer.Add(hitObject = new TestDrawableHitObject())); + AddAssert("combo colour is red", () => hitObject.AccentColour.Value == Color4.Red); + + AddStep("change combo", () => hitObject.HitObject.ComboIndex = 1); + AddAssert("combo colour is green", () => hitObject.AccentColour.Value == Color4.Green); + } + + private class TestDrawableHitObject : DrawableHitObject + { + public TestDrawableHitObject() + : base(new TestHitObjectWithCombo()) + { + } + } + + private class TestHitObjectWithCombo : HitObject, IHasComboInformation + { + public bool NewCombo { get; } = false; + public int ComboOffset { get; } = 0; + + public Bindable IndexInCurrentComboBindable { get; } = new Bindable(); + + public int IndexInCurrentCombo + { + get => IndexInCurrentComboBindable.Value; + set => IndexInCurrentComboBindable.Value = value; + } + + public Bindable ComboIndexBindable { get; } = new Bindable(); + + public int ComboIndex + { + get => ComboIndexBindable.Value; + set => ComboIndexBindable.Value = value; + } + + public Bindable LastInComboBindable { get; } = new Bindable(); + + public bool LastInCombo + { + get => LastInComboBindable.Value; + set => LastInComboBindable.Value = value; + } + } + + private class TestSkin : ISkin + { + public readonly List ComboColours = new List + { + Color4.Red, + Color4.Green + }; + + public Drawable GetDrawableComponent(ISkinComponent component) => throw new NotImplementedException(); + + public Texture GetTexture(string componentName) => throw new NotImplementedException(); + + public SampleChannel GetSample(ISampleInfo sampleInfo) => throw new NotImplementedException(); + + public IBindable GetConfig(TLookup lookup) + { + switch (lookup) + { + case GlobalSkinConfiguration global: + switch (global) + { + case GlobalSkinConfiguration.ComboColours: + return SkinUtils.As(new Bindable>(ComboColours)); + } + + break; + } + + throw new NotImplementedException(); + } + } + } +} diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index 7f3bfd3b5c..0948452ceb 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -128,7 +128,7 @@ namespace osu.Game.Rulesets.Objects.Drawables if (HitObject is IHasComboInformation combo) { comboIndexBindable = combo.ComboIndexBindable.GetBoundCopy(); - comboIndexBindable.BindValueChanged(_ => updateAccentColour()); + comboIndexBindable.BindValueChanged(_ => updateAccentColour(), true); } updateState(ArmedState.Idle, true); From 07286c0cfc106dfd49fb8370bfe5490ed2bb597f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 18 Oct 2019 18:21:53 +0900 Subject: [PATCH 19/19] Fix editor's clock not being processed unless composer is loaded --- osu.Game/Rulesets/Edit/HitObjectComposer.cs | 3 ++- osu.Game/Screens/Edit/Editor.cs | 6 ++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index a267d7c44d..038d6a320a 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -57,7 +57,8 @@ namespace osu.Game.Rulesets.Edit { drawableRulesetWrapper = new DrawableEditRulesetWrapper(CreateDrawableRuleset(Ruleset, workingBeatmap, Array.Empty())) { - Clock = framedClock + Clock = framedClock, + ProcessCustomClock = false }; } catch (Exception e) diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 7f08c2f8b9..35408e4003 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -173,6 +173,12 @@ namespace osu.Game.Screens.Edit bottomBackground.Colour = colours.Gray2; } + protected override void Update() + { + base.Update(); + clock.ProcessFrame(); + } + protected override bool OnKeyDown(KeyDownEvent e) { switch (e.Key)