From 40d0700fa514da9d4df1abd78fbe3a74a053d985 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 5 Dec 2019 13:43:38 +0900 Subject: [PATCH 01/89] Add structure for path control points --- osu.Game/Rulesets/Objects/PathControlPoint.cs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 osu.Game/Rulesets/Objects/PathControlPoint.cs diff --git a/osu.Game/Rulesets/Objects/PathControlPoint.cs b/osu.Game/Rulesets/Objects/PathControlPoint.cs new file mode 100644 index 0000000000..40a8b0251b --- /dev/null +++ b/osu.Game/Rulesets/Objects/PathControlPoint.cs @@ -0,0 +1,16 @@ +using System; +using osu.Framework.Bindables; +using osu.Game.Rulesets.Objects.Types; +using osuTK; + +namespace osu.Game.Rulesets.Objects +{ + public class PathControlPoint : IEquatable + { + public readonly Bindable Position = new Bindable(); + + public readonly Bindable Type = new Bindable(); + + public bool Equals(PathControlPoint other) => Position.Value == other.Position.Value && Type.Value == other.Type.Value; + } +} From 3e0f499e72a3149920ea13f16fd859a3e35fea6d Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 5 Dec 2019 14:38:21 +0900 Subject: [PATCH 02/89] Add xmldocs --- osu.Game/Rulesets/Objects/PathControlPoint.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/osu.Game/Rulesets/Objects/PathControlPoint.cs b/osu.Game/Rulesets/Objects/PathControlPoint.cs index 40a8b0251b..fe66fb79cb 100644 --- a/osu.Game/Rulesets/Objects/PathControlPoint.cs +++ b/osu.Game/Rulesets/Objects/PathControlPoint.cs @@ -7,8 +7,15 @@ namespace osu.Game.Rulesets.Objects { public class PathControlPoint : IEquatable { + /// + /// The position of this . + /// public readonly Bindable Position = new Bindable(); + /// + /// The type of path segment starting at this . + /// If null, this will be a part of the previous path segment. + /// public readonly Bindable Type = new Bindable(); public bool Equals(PathControlPoint other) => Position.Value == other.Position.Value && Type.Value == other.Type.Value; From 0149e476739c1e86ddd3b4c32d7ec4292fe37355 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 5 Dec 2019 14:38:32 +0900 Subject: [PATCH 03/89] Expose general control point change event --- osu.Game/Rulesets/Objects/PathControlPoint.cs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/osu.Game/Rulesets/Objects/PathControlPoint.cs b/osu.Game/Rulesets/Objects/PathControlPoint.cs index fe66fb79cb..d68ef4112b 100644 --- a/osu.Game/Rulesets/Objects/PathControlPoint.cs +++ b/osu.Game/Rulesets/Objects/PathControlPoint.cs @@ -18,6 +18,17 @@ namespace osu.Game.Rulesets.Objects /// public readonly Bindable Type = new Bindable(); + /// + /// Invoked when any property of this is changed. + /// + internal event Action Changed; + + public PathControlPoint() + { + Position.ValueChanged += _ => Changed?.Invoke(); + Type.ValueChanged += _ => Changed?.Invoke(); + } + public bool Equals(PathControlPoint other) => Position.Value == other.Position.Value && Type.Value == other.Type.Value; } } From 5e9b739b6718c44b643679fdb72b94f0ee378d91 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 5 Dec 2019 16:45:02 +0900 Subject: [PATCH 04/89] Re-implement slider paths to support multiple segments --- .../Visual/Gameplay/TestSceneSliderPath2.cs | 159 ++++++++++ osu.Game/Rulesets/Objects/SliderPath2.cs | 274 ++++++++++++++++++ 2 files changed, 433 insertions(+) create mode 100644 osu.Game.Tests/Visual/Gameplay/TestSceneSliderPath2.cs create mode 100644 osu.Game/Rulesets/Objects/SliderPath2.cs diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSliderPath2.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSliderPath2.cs new file mode 100644 index 0000000000..08d54fcdda --- /dev/null +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSliderPath2.cs @@ -0,0 +1,159 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using System.Linq; +using NUnit.Framework; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Lines; +using osu.Framework.MathUtils; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Types; +using osuTK; + +namespace osu.Game.Tests.Visual.Gameplay +{ + public class TestSceneSliderPath2 : OsuTestScene + { + private readonly SmoothPath drawablePath; + private SliderPath2 path; + + public TestSceneSliderPath2() + { + Child = drawablePath = new SmoothPath + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre + }; + } + + [SetUp] + public void Setup() => Schedule(() => + { + path = new SliderPath2(); + }); + + protected override void Update() + { + base.Update(); + + if (path != null) + { + List vertices = new List(); + path.GetPathToProgress(vertices, 0, 1); + + drawablePath.Vertices = vertices; + } + } + + [Test] + public void TestEmptyPath() + { + } + + [TestCase(PathType.Linear)] + [TestCase(PathType.Bezier)] + [TestCase(PathType.Catmull)] + [TestCase(PathType.PerfectCurve)] + public void TestSingleSegment(PathType type) + => AddStep("create path", () => path.ControlPoints.AddRange(createSegment(type, Vector2.Zero, new Vector2(0, 100), new Vector2(100)))); + + [TestCase(PathType.Linear)] + [TestCase(PathType.Bezier)] + [TestCase(PathType.Catmull)] + [TestCase(PathType.PerfectCurve)] + public void TestMultipleSegment(PathType type) + { + AddStep("create path", () => + { + path.ControlPoints.AddRange(createSegment(PathType.Linear, Vector2.Zero)); + path.ControlPoints.AddRange(createSegment(type, new Vector2(0, 100), new Vector2(100), Vector2.Zero)); + }); + } + + [Test] + public void TestAddControlPoint() + { + AddStep("create path", () => path.ControlPoints.AddRange(createSegment(PathType.Linear, Vector2.Zero, new Vector2(0, 100)))); + AddStep("add point", () => path.ControlPoints.Add(new PathControlPoint { Position = { Value = new Vector2(100) } })); + } + + [Test] + public void TestInsertControlPoint() + { + AddStep("create path", () => path.ControlPoints.AddRange(createSegment(PathType.Linear, Vector2.Zero, new Vector2(100)))); + AddStep("insert point", () => path.ControlPoints.Insert(1, new PathControlPoint { Position = { Value = new Vector2(0, 100) } })); + } + + [Test] + public void TestRemoveControlPoint() + { + AddStep("create path", () => path.ControlPoints.AddRange(createSegment(PathType.Linear, Vector2.Zero, new Vector2(0, 100), new Vector2(100)))); + AddStep("remove second point", () => path.ControlPoints.RemoveAt(1)); + } + + [Test] + public void TestChangePathType() + { + AddStep("create path", () => path.ControlPoints.AddRange(createSegment(PathType.Linear, Vector2.Zero, new Vector2(0, 100), new Vector2(100)))); + AddStep("change type to bezier", () => path.ControlPoints[0].Type.Value = PathType.Bezier); + } + + [Test] + public void TestAddSegmentByChangingType() + { + AddStep("create path", () => path.ControlPoints.AddRange(createSegment(PathType.Linear, Vector2.Zero, new Vector2(0, 100), new Vector2(100), new Vector2(100, 0)))); + AddStep("change second point type to bezier", () => path.ControlPoints[1].Type.Value = PathType.Bezier); + } + + [Test] + public void TestRemoveSegmentByChangingType() + { + AddStep("create path", () => + { + path.ControlPoints.AddRange(createSegment(PathType.Linear, Vector2.Zero, new Vector2(0, 100), new Vector2(100), new Vector2(100, 0))); + path.ControlPoints[1].Type.Value = PathType.Bezier; + }); + + AddStep("change second point type to null", () => path.ControlPoints[1].Type.Value = null); + } + + [Test] + public void TestRemoveSegmentByRemovingControlPoint() + { + AddStep("create path", () => + { + path.ControlPoints.AddRange(createSegment(PathType.Linear, Vector2.Zero, new Vector2(0, 100), new Vector2(100), new Vector2(100, 0))); + path.ControlPoints[1].Type.Value = PathType.Bezier; + }); + + AddStep("remove second point", () => path.ControlPoints.RemoveAt(1)); + } + + [TestCase(2)] + [TestCase(4)] + public void TestPerfectCurveFallbackScenarios(int points) + { + AddStep("create path", () => + { + switch (points) + { + case 2: + path.ControlPoints.AddRange(createSegment(PathType.PerfectCurve, Vector2.Zero, new Vector2(0, 100))); + break; + case 4: + path.ControlPoints.AddRange(createSegment(PathType.PerfectCurve, Vector2.Zero, new Vector2(0, 100), new Vector2(100), new Vector2(100, 0))); + break; + } + }); + + } + + private List createSegment(PathType type, params Vector2[] controlPoints) + { + var points = controlPoints.Select(p => new PathControlPoint { Position = { Value = p } }).ToList(); + points[0].Type.Value = type; + return points; + } + } +} diff --git a/osu.Game/Rulesets/Objects/SliderPath2.cs b/osu.Game/Rulesets/Objects/SliderPath2.cs new file mode 100644 index 0000000000..560313a4d5 --- /dev/null +++ b/osu.Game/Rulesets/Objects/SliderPath2.cs @@ -0,0 +1,274 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using Newtonsoft.Json; +using osu.Framework.Bindables; +using osu.Framework.Caching; +using osu.Framework.MathUtils; +using osu.Game.Rulesets.Objects.Types; +using osuTK; + +namespace osu.Game.Rulesets.Objects +{ + public class SliderPath2 + { + /// + /// The user-set distance of the path. If non-null, will match this value, + /// and the path will be shortened/lengthened to match this length. + /// + public readonly double? ExpectedDistance; + + /// + /// The control points of the path. + /// + public readonly BindableList ControlPoints = new BindableList(); + + private readonly Cached pathCache = new Cached(); + + private readonly List calculatedPath = new List(); + private readonly List cumulativeLength = new List(); + + /// + /// Creates a new . + /// + /// A user-set distance of the path that may be shorter or longer than the true distance between all control points. + /// The path will be shortened/lengthened to match this length. If null, the path will use the true distance between all control points. + [JsonConstructor] + public SliderPath2(double? expectedDistance = null) + { + ExpectedDistance = expectedDistance; + + ControlPoints.ItemsAdded += items => + { + foreach (var c in items) + c.Changed += onControlPointChanged; + + onControlPointChanged(); + }; + + ControlPoints.ItemsRemoved += items => + { + foreach (var c in items) + c.Changed -= onControlPointChanged; + + onControlPointChanged(); + }; + + void onControlPointChanged() => pathCache.Invalidate(); + } + + /// + /// The distance of the path after lengthening/shortening to account for . + /// + [JsonIgnore] + public double Distance + { + get + { + ensureValid(); + return cumulativeLength.Count == 0 ? 0 : cumulativeLength[cumulativeLength.Count - 1]; + } + } + + /// + /// Computes the slider path until a given progress that ranges from 0 (beginning of the slider) + /// to 1 (end of the slider) and stores the generated path in the given list. + /// + /// The list to be filled with the computed path. + /// Start progress. Ranges from 0 (beginning of the slider) to 1 (end of the slider). + /// End progress. Ranges from 0 (beginning of the slider) to 1 (end of the slider). + public void GetPathToProgress(List path, double p0, double p1) + { + ensureValid(); + + double d0 = progressToDistance(p0); + double d1 = progressToDistance(p1); + + path.Clear(); + + int i = 0; + + for (; i < calculatedPath.Count && cumulativeLength[i] < d0; ++i) + { + } + + path.Add(interpolateVertices(i, d0)); + + for (; i < calculatedPath.Count && cumulativeLength[i] <= d1; ++i) + path.Add(calculatedPath[i]); + + path.Add(interpolateVertices(i, d1)); + } + + /// + /// Computes the position on the slider at a given progress that ranges from 0 (beginning of the path) + /// to 1 (end of the path). + /// + /// Ranges from 0 (beginning of the path) to 1 (end of the path). + /// + public Vector2 PositionAt(double progress) + { + ensureValid(); + + double d = progressToDistance(progress); + return interpolateVertices(indexOfDistance(d), d); + } + + private void ensureValid() + { + if (pathCache.IsValid) + return; + + calculatePath(); + calculateCumulativeLength(); + + pathCache.Validate(); + } + + private void calculatePath() + { + calculatedPath.Clear(); + + if (ControlPoints.Count == 0) + return; + + if (ControlPoints[0].Type.Value == null) + throw new InvalidOperationException($"The first control point in a {nameof(SliderPath2)} must have a non-null type."); + + Vector2[] vertices = new Vector2[ControlPoints.Count]; + for (int i = 0; i < ControlPoints.Count; i++) + vertices[i] = ControlPoints[i].Position.Value; + + int start = 0; + + for (int i = 0; i < ControlPoints.Count; i++) + { + if (ControlPoints[i].Type.Value == null && i < ControlPoints.Count - 1) + continue; + + Debug.Assert(ControlPoints[start].Type.Value.HasValue); + + // The current vertex ends the segment + var segmentVertices = vertices.AsSpan().Slice(start, i - start + 1); + var segmentType = ControlPoints[start].Type.Value.Value; + + foreach (Vector2 t in computeSubPath(segmentVertices, segmentType)) + { + if (calculatedPath.Count == 0 || calculatedPath.Last() != t) + calculatedPath.Add(t); + } + + // Start the new segment at the current vertex + start = i; + } + + static List computeSubPath(ReadOnlySpan subControlPoints, PathType type) + { + switch (type) + { + case PathType.Linear: + return PathApproximator.ApproximateLinear(subControlPoints); + + case PathType.PerfectCurve: + if (subControlPoints.Length != 3) + break; + + List subpath = PathApproximator.ApproximateCircularArc(subControlPoints); + + // If for some reason a circular arc could not be fit to the 3 given points, fall back to a numerically stable bezier approximation. + if (subpath.Count == 0) + break; + + return subpath; + + case PathType.Catmull: + return PathApproximator.ApproximateCatmull(subControlPoints); + } + + return PathApproximator.ApproximateBezier(subControlPoints); + } + } + + private void calculateCumulativeLength() + { + double l = 0; + + cumulativeLength.Clear(); + cumulativeLength.Add(l); + + for (int i = 0; i < calculatedPath.Count - 1; ++i) + { + Vector2 diff = calculatedPath[i + 1] - calculatedPath[i]; + double d = diff.Length; + + // Shorted slider paths that are too long compared to the expected distance + if (ExpectedDistance.HasValue && ExpectedDistance - l < d) + { + calculatedPath[i + 1] = calculatedPath[i] + diff * (float)((ExpectedDistance - l) / d); + calculatedPath.RemoveRange(i + 2, calculatedPath.Count - 2 - i); + + l = ExpectedDistance.Value; + cumulativeLength.Add(l); + break; + } + + l += d; + cumulativeLength.Add(l); + } + + // Lengthen slider paths that are too short compared to the expected distance + if (ExpectedDistance.HasValue && l < ExpectedDistance && calculatedPath.Count > 1) + { + Vector2 diff = calculatedPath[calculatedPath.Count - 1] - calculatedPath[calculatedPath.Count - 2]; + double d = diff.Length; + + if (d <= 0) + return; + + calculatedPath[calculatedPath.Count - 1] += diff * (float)((ExpectedDistance - l) / d); + cumulativeLength[calculatedPath.Count - 1] = ExpectedDistance.Value; + } + } + + private int indexOfDistance(double d) + { + int i = cumulativeLength.BinarySearch(d); + if (i < 0) i = ~i; + + return i; + } + + private double progressToDistance(double progress) + { + return Math.Clamp(progress, 0, 1) * Distance; + } + + private Vector2 interpolateVertices(int i, double d) + { + if (calculatedPath.Count == 0) + return Vector2.Zero; + + if (i <= 0) + return calculatedPath.First(); + if (i >= calculatedPath.Count) + return calculatedPath.Last(); + + Vector2 p0 = calculatedPath[i - 1]; + Vector2 p1 = calculatedPath[i]; + + double d0 = cumulativeLength[i - 1]; + double d1 = cumulativeLength[i]; + + // Avoid division by and almost-zero number in case two points are extremely close to each other. + if (Precision.AlmostEquals(d0, d1)) + return p0; + + double w = (d - d0) / (d1 - d0); + return p0 + (p1 - p0) * (float)w; + } + } +} From 2702edfa555fc98b68c9e11c45ce30b30ba02737 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 5 Dec 2019 17:49:32 +0900 Subject: [PATCH 05/89] Rename new path, replace existing one --- ...eSliderPath2.cs => TestSceneSliderPath.cs} | 8 +- osu.Game/Rulesets/Objects/SliderPath.cs | 177 +++++------ osu.Game/Rulesets/Objects/SliderPath2.cs | 274 ------------------ 3 files changed, 93 insertions(+), 366 deletions(-) rename osu.Game.Tests/Visual/Gameplay/{TestSceneSliderPath2.cs => TestSceneSliderPath.cs} (97%) delete mode 100644 osu.Game/Rulesets/Objects/SliderPath2.cs diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSliderPath2.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSliderPath.cs similarity index 97% rename from osu.Game.Tests/Visual/Gameplay/TestSceneSliderPath2.cs rename to osu.Game.Tests/Visual/Gameplay/TestSceneSliderPath.cs index 08d54fcdda..fe2cc188a5 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSliderPath2.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSliderPath.cs @@ -13,12 +13,12 @@ using osuTK; namespace osu.Game.Tests.Visual.Gameplay { - public class TestSceneSliderPath2 : OsuTestScene + public class TestSceneSliderPath : OsuTestScene { private readonly SmoothPath drawablePath; - private SliderPath2 path; + private SliderPath path; - public TestSceneSliderPath2() + public TestSceneSliderPath() { Child = drawablePath = new SmoothPath { @@ -30,7 +30,7 @@ namespace osu.Game.Tests.Visual.Gameplay [SetUp] public void Setup() => Schedule(() => { - path = new SliderPath2(); + path = new SliderPath(); }); protected override void Update() diff --git a/osu.Game/Rulesets/Objects/SliderPath.cs b/osu.Game/Rulesets/Objects/SliderPath.cs index ae6aad5b9c..cc2b537afc 100644 --- a/osu.Game/Rulesets/Objects/SliderPath.cs +++ b/osu.Game/Rulesets/Objects/SliderPath.cs @@ -1,17 +1,20 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using Newtonsoft.Json; +using osu.Framework.Bindables; +using osu.Framework.Caching; using osu.Framework.MathUtils; using osu.Game.Rulesets.Objects.Types; using osuTK; namespace osu.Game.Rulesets.Objects { - public struct SliderPath : IEquatable + public class SliderPath { /// /// The user-set distance of the path. If non-null, will match this value, @@ -20,49 +23,47 @@ namespace osu.Game.Rulesets.Objects public readonly double? ExpectedDistance; /// - /// The type of path. + /// The control points of the path. /// - public readonly PathType Type; + public readonly BindableList ControlPoints = new BindableList(); - [JsonProperty] - private Vector2[] controlPoints; + public readonly List Test = new List(); - private List calculatedPath; - private List cumulativeLength; + private readonly Cached pathCache = new Cached(); - private bool isInitialised; + private readonly List calculatedPath = new List(); + private readonly List cumulativeLength = new List(); /// /// Creates a new . /// - /// The type of path. - /// The control points of the path. - /// A user-set distance of the path that may be shorter or longer than the true distance between all - /// . The path will be shortened/lengthened to match this length. - /// If null, the path will use the true distance between all . + /// An optional set of s to initialise the path with. + /// A user-set distance of the path that may be shorter or longer than the true distance between all control points. + /// The path will be shortened/lengthened to match this length. If null, the path will use the true distance between all control points. [JsonConstructor] - public SliderPath(PathType type, Vector2[] controlPoints, double? expectedDistance = null) + public SliderPath(PathControlPoint[] controlPoints = null, double? expectedDistance = null) { - this = default; - this.controlPoints = controlPoints; - - Type = type; ExpectedDistance = expectedDistance; - ensureInitialised(); - } - - /// - /// The control points of the path. - /// - [JsonIgnore] - public ReadOnlySpan ControlPoints - { - get + ControlPoints.ItemsAdded += items => { - ensureInitialised(); - return controlPoints.AsSpan(); - } + foreach (var c in items) + c.Changed += onControlPointChanged; + + onControlPointChanged(); + }; + + ControlPoints.ItemsRemoved += items => + { + foreach (var c in items) + c.Changed -= onControlPointChanged; + + onControlPointChanged(); + }; + + ControlPoints.AddRange(controlPoints); + + void onControlPointChanged() => pathCache.Invalidate(); } /// @@ -73,7 +74,7 @@ namespace osu.Game.Rulesets.Objects { get { - ensureInitialised(); + ensureValid(); return cumulativeLength.Count == 0 ? 0 : cumulativeLength[cumulativeLength.Count - 1]; } } @@ -87,7 +88,7 @@ namespace osu.Game.Rulesets.Objects /// End progress. Ranges from 0 (beginning of the slider) to 1 (end of the slider). public void GetPathToProgress(List path, double p0, double p1) { - ensureInitialised(); + ensureValid(); double d0 = progressToDistance(p0); double d1 = progressToDistance(p1); @@ -116,82 +117,84 @@ namespace osu.Game.Rulesets.Objects /// public Vector2 PositionAt(double progress) { - ensureInitialised(); + ensureValid(); double d = progressToDistance(progress); return interpolateVertices(indexOfDistance(d), d); } - private void ensureInitialised() + private void ensureValid() { - if (isInitialised) + if (pathCache.IsValid) return; - isInitialised = true; - - controlPoints ??= Array.Empty(); - calculatedPath = new List(); - cumulativeLength = new List(); - calculatePath(); calculateCumulativeLength(); - } - private List calculateSubpath(ReadOnlySpan subControlPoints) - { - switch (Type) - { - case PathType.Linear: - return PathApproximator.ApproximateLinear(subControlPoints); - - case PathType.PerfectCurve: - //we can only use CircularArc iff we have exactly three control points and no dissection. - if (ControlPoints.Length != 3 || subControlPoints.Length != 3) - break; - - // Here we have exactly 3 control points. Attempt to fit a circular arc. - List subpath = PathApproximator.ApproximateCircularArc(subControlPoints); - - // If for some reason a circular arc could not be fit to the 3 given points, fall back to a numerically stable bezier approximation. - if (subpath.Count == 0) - break; - - return subpath; - - case PathType.Catmull: - return PathApproximator.ApproximateCatmull(subControlPoints); - } - - return PathApproximator.ApproximateBezier(subControlPoints); + pathCache.Validate(); } private void calculatePath() { calculatedPath.Clear(); - // Sliders may consist of various subpaths separated by two consecutive vertices - // with the same position. The following loop parses these subpaths and computes - // their shape independently, consecutively appending them to calculatedPath. + if (ControlPoints.Count == 0) + return; + + if (ControlPoints[0].Type.Value == null) + throw new InvalidOperationException($"The first control point in a {nameof(SliderPath)} must have a non-null type."); + + Vector2[] vertices = new Vector2[ControlPoints.Count]; + for (int i = 0; i < ControlPoints.Count; i++) + vertices[i] = ControlPoints[i].Position.Value; int start = 0; - int end = 0; - for (int i = 0; i < ControlPoints.Length; ++i) + for (int i = 0; i < ControlPoints.Count; i++) { - end++; + if (ControlPoints[i].Type.Value == null && i < ControlPoints.Count - 1) + continue; - if (i == ControlPoints.Length - 1 || ControlPoints[i] == ControlPoints[i + 1]) + Debug.Assert(ControlPoints[start].Type.Value.HasValue); + + // The current vertex ends the segment + var segmentVertices = vertices.AsSpan().Slice(start, i - start + 1); + var segmentType = ControlPoints[start].Type.Value.Value; + + foreach (Vector2 t in computeSubPath(segmentVertices, segmentType)) { - ReadOnlySpan cpSpan = ControlPoints.Slice(start, end - start); - - foreach (Vector2 t in calculateSubpath(cpSpan)) - { - if (calculatedPath.Count == 0 || calculatedPath.Last() != t) - calculatedPath.Add(t); - } - - start = end; + if (calculatedPath.Count == 0 || calculatedPath.Last() != t) + calculatedPath.Add(t); } + + // Start the new segment at the current vertex + start = i; + } + + static List computeSubPath(ReadOnlySpan subControlPoints, PathType type) + { + switch (type) + { + case PathType.Linear: + return PathApproximator.ApproximateLinear(subControlPoints); + + case PathType.PerfectCurve: + if (subControlPoints.Length != 3) + break; + + List subpath = PathApproximator.ApproximateCircularArc(subControlPoints); + + // If for some reason a circular arc could not be fit to the 3 given points, fall back to a numerically stable bezier approximation. + if (subpath.Count == 0) + break; + + return subpath; + + case PathType.Catmull: + return PathApproximator.ApproximateCatmull(subControlPoints); + } + + return PathApproximator.ApproximateBezier(subControlPoints); } } @@ -272,7 +275,5 @@ namespace osu.Game.Rulesets.Objects double w = (d - d0) / (d1 - d0); return p0 + (p1 - p0) * (float)w; } - - public bool Equals(SliderPath other) => ControlPoints.SequenceEqual(other.ControlPoints) && ExpectedDistance == other.ExpectedDistance && Type == other.Type; } } diff --git a/osu.Game/Rulesets/Objects/SliderPath2.cs b/osu.Game/Rulesets/Objects/SliderPath2.cs deleted file mode 100644 index 560313a4d5..0000000000 --- a/osu.Game/Rulesets/Objects/SliderPath2.cs +++ /dev/null @@ -1,274 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Linq; -using Newtonsoft.Json; -using osu.Framework.Bindables; -using osu.Framework.Caching; -using osu.Framework.MathUtils; -using osu.Game.Rulesets.Objects.Types; -using osuTK; - -namespace osu.Game.Rulesets.Objects -{ - public class SliderPath2 - { - /// - /// The user-set distance of the path. If non-null, will match this value, - /// and the path will be shortened/lengthened to match this length. - /// - public readonly double? ExpectedDistance; - - /// - /// The control points of the path. - /// - public readonly BindableList ControlPoints = new BindableList(); - - private readonly Cached pathCache = new Cached(); - - private readonly List calculatedPath = new List(); - private readonly List cumulativeLength = new List(); - - /// - /// Creates a new . - /// - /// A user-set distance of the path that may be shorter or longer than the true distance between all control points. - /// The path will be shortened/lengthened to match this length. If null, the path will use the true distance between all control points. - [JsonConstructor] - public SliderPath2(double? expectedDistance = null) - { - ExpectedDistance = expectedDistance; - - ControlPoints.ItemsAdded += items => - { - foreach (var c in items) - c.Changed += onControlPointChanged; - - onControlPointChanged(); - }; - - ControlPoints.ItemsRemoved += items => - { - foreach (var c in items) - c.Changed -= onControlPointChanged; - - onControlPointChanged(); - }; - - void onControlPointChanged() => pathCache.Invalidate(); - } - - /// - /// The distance of the path after lengthening/shortening to account for . - /// - [JsonIgnore] - public double Distance - { - get - { - ensureValid(); - return cumulativeLength.Count == 0 ? 0 : cumulativeLength[cumulativeLength.Count - 1]; - } - } - - /// - /// Computes the slider path until a given progress that ranges from 0 (beginning of the slider) - /// to 1 (end of the slider) and stores the generated path in the given list. - /// - /// The list to be filled with the computed path. - /// Start progress. Ranges from 0 (beginning of the slider) to 1 (end of the slider). - /// End progress. Ranges from 0 (beginning of the slider) to 1 (end of the slider). - public void GetPathToProgress(List path, double p0, double p1) - { - ensureValid(); - - double d0 = progressToDistance(p0); - double d1 = progressToDistance(p1); - - path.Clear(); - - int i = 0; - - for (; i < calculatedPath.Count && cumulativeLength[i] < d0; ++i) - { - } - - path.Add(interpolateVertices(i, d0)); - - for (; i < calculatedPath.Count && cumulativeLength[i] <= d1; ++i) - path.Add(calculatedPath[i]); - - path.Add(interpolateVertices(i, d1)); - } - - /// - /// Computes the position on the slider at a given progress that ranges from 0 (beginning of the path) - /// to 1 (end of the path). - /// - /// Ranges from 0 (beginning of the path) to 1 (end of the path). - /// - public Vector2 PositionAt(double progress) - { - ensureValid(); - - double d = progressToDistance(progress); - return interpolateVertices(indexOfDistance(d), d); - } - - private void ensureValid() - { - if (pathCache.IsValid) - return; - - calculatePath(); - calculateCumulativeLength(); - - pathCache.Validate(); - } - - private void calculatePath() - { - calculatedPath.Clear(); - - if (ControlPoints.Count == 0) - return; - - if (ControlPoints[0].Type.Value == null) - throw new InvalidOperationException($"The first control point in a {nameof(SliderPath2)} must have a non-null type."); - - Vector2[] vertices = new Vector2[ControlPoints.Count]; - for (int i = 0; i < ControlPoints.Count; i++) - vertices[i] = ControlPoints[i].Position.Value; - - int start = 0; - - for (int i = 0; i < ControlPoints.Count; i++) - { - if (ControlPoints[i].Type.Value == null && i < ControlPoints.Count - 1) - continue; - - Debug.Assert(ControlPoints[start].Type.Value.HasValue); - - // The current vertex ends the segment - var segmentVertices = vertices.AsSpan().Slice(start, i - start + 1); - var segmentType = ControlPoints[start].Type.Value.Value; - - foreach (Vector2 t in computeSubPath(segmentVertices, segmentType)) - { - if (calculatedPath.Count == 0 || calculatedPath.Last() != t) - calculatedPath.Add(t); - } - - // Start the new segment at the current vertex - start = i; - } - - static List computeSubPath(ReadOnlySpan subControlPoints, PathType type) - { - switch (type) - { - case PathType.Linear: - return PathApproximator.ApproximateLinear(subControlPoints); - - case PathType.PerfectCurve: - if (subControlPoints.Length != 3) - break; - - List subpath = PathApproximator.ApproximateCircularArc(subControlPoints); - - // If for some reason a circular arc could not be fit to the 3 given points, fall back to a numerically stable bezier approximation. - if (subpath.Count == 0) - break; - - return subpath; - - case PathType.Catmull: - return PathApproximator.ApproximateCatmull(subControlPoints); - } - - return PathApproximator.ApproximateBezier(subControlPoints); - } - } - - private void calculateCumulativeLength() - { - double l = 0; - - cumulativeLength.Clear(); - cumulativeLength.Add(l); - - for (int i = 0; i < calculatedPath.Count - 1; ++i) - { - Vector2 diff = calculatedPath[i + 1] - calculatedPath[i]; - double d = diff.Length; - - // Shorted slider paths that are too long compared to the expected distance - if (ExpectedDistance.HasValue && ExpectedDistance - l < d) - { - calculatedPath[i + 1] = calculatedPath[i] + diff * (float)((ExpectedDistance - l) / d); - calculatedPath.RemoveRange(i + 2, calculatedPath.Count - 2 - i); - - l = ExpectedDistance.Value; - cumulativeLength.Add(l); - break; - } - - l += d; - cumulativeLength.Add(l); - } - - // Lengthen slider paths that are too short compared to the expected distance - if (ExpectedDistance.HasValue && l < ExpectedDistance && calculatedPath.Count > 1) - { - Vector2 diff = calculatedPath[calculatedPath.Count - 1] - calculatedPath[calculatedPath.Count - 2]; - double d = diff.Length; - - if (d <= 0) - return; - - calculatedPath[calculatedPath.Count - 1] += diff * (float)((ExpectedDistance - l) / d); - cumulativeLength[calculatedPath.Count - 1] = ExpectedDistance.Value; - } - } - - private int indexOfDistance(double d) - { - int i = cumulativeLength.BinarySearch(d); - if (i < 0) i = ~i; - - return i; - } - - private double progressToDistance(double progress) - { - return Math.Clamp(progress, 0, 1) * Distance; - } - - private Vector2 interpolateVertices(int i, double d) - { - if (calculatedPath.Count == 0) - return Vector2.Zero; - - if (i <= 0) - return calculatedPath.First(); - if (i >= calculatedPath.Count) - return calculatedPath.Last(); - - Vector2 p0 = calculatedPath[i - 1]; - Vector2 p1 = calculatedPath[i]; - - double d0 = cumulativeLength[i - 1]; - double d1 = cumulativeLength[i]; - - // Avoid division by and almost-zero number in case two points are extremely close to each other. - if (Precision.AlmostEquals(d0, d1)) - return p0; - - double w = (d - d0) / (d1 - d0); - return p0 + (p1 - p0) * (float)w; - } - } -} From 986ac1cee4df72dc07c93f15f24032bc714a1edc Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 5 Dec 2019 17:49:54 +0900 Subject: [PATCH 06/89] Make expected distance a bindable --- osu.Game/Rulesets/Objects/SliderPath.cs | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/osu.Game/Rulesets/Objects/SliderPath.cs b/osu.Game/Rulesets/Objects/SliderPath.cs index cc2b537afc..9d68e1337a 100644 --- a/osu.Game/Rulesets/Objects/SliderPath.cs +++ b/osu.Game/Rulesets/Objects/SliderPath.cs @@ -20,7 +20,7 @@ namespace osu.Game.Rulesets.Objects /// The user-set distance of the path. If non-null, will match this value, /// and the path will be shortened/lengthened to match this length. /// - public readonly double? ExpectedDistance; + public readonly Bindable ExpectedDistance = new Bindable(); /// /// The control points of the path. @@ -43,7 +43,8 @@ namespace osu.Game.Rulesets.Objects [JsonConstructor] public SliderPath(PathControlPoint[] controlPoints = null, double? expectedDistance = null) { - ExpectedDistance = expectedDistance; + ExpectedDistance.Value = expectedDistance; + ExpectedDistance.ValueChanged += _ => pathCache.Invalidate(); ControlPoints.ItemsAdded += items => { @@ -205,18 +206,20 @@ namespace osu.Game.Rulesets.Objects cumulativeLength.Clear(); cumulativeLength.Add(l); + double? expectedDistance = ExpectedDistance.Value; + for (int i = 0; i < calculatedPath.Count - 1; ++i) { Vector2 diff = calculatedPath[i + 1] - calculatedPath[i]; double d = diff.Length; // Shorted slider paths that are too long compared to the expected distance - if (ExpectedDistance.HasValue && ExpectedDistance - l < d) + if (expectedDistance.HasValue && expectedDistance - l < d) { - calculatedPath[i + 1] = calculatedPath[i] + diff * (float)((ExpectedDistance - l) / d); + calculatedPath[i + 1] = calculatedPath[i] + diff * (float)((expectedDistance - l) / d); calculatedPath.RemoveRange(i + 2, calculatedPath.Count - 2 - i); - l = ExpectedDistance.Value; + l = expectedDistance.Value; cumulativeLength.Add(l); break; } @@ -226,7 +229,7 @@ namespace osu.Game.Rulesets.Objects } // Lengthen slider paths that are too short compared to the expected distance - if (ExpectedDistance.HasValue && l < ExpectedDistance && calculatedPath.Count > 1) + if (expectedDistance.HasValue && l < expectedDistance && calculatedPath.Count > 1) { Vector2 diff = calculatedPath[calculatedPath.Count - 1] - calculatedPath[calculatedPath.Count - 2]; double d = diff.Length; @@ -234,8 +237,8 @@ namespace osu.Game.Rulesets.Objects if (d <= 0) return; - calculatedPath[calculatedPath.Count - 1] += diff * (float)((ExpectedDistance - l) / d); - cumulativeLength[calculatedPath.Count - 1] = ExpectedDistance.Value; + calculatedPath[calculatedPath.Count - 1] += diff * (float)((expectedDistance - l) / d); + cumulativeLength[calculatedPath.Count - 1] = expectedDistance.Value; } } From 1585d83b969016e287cb9c2af06abdbe2df431f3 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 5 Dec 2019 18:19:42 +0900 Subject: [PATCH 07/89] Add legacy constructor --- osu.Game/Rulesets/Objects/SliderPath.cs | 33 +++++++++++++++++++------ 1 file changed, 25 insertions(+), 8 deletions(-) diff --git a/osu.Game/Rulesets/Objects/SliderPath.cs b/osu.Game/Rulesets/Objects/SliderPath.cs index 9d68e1337a..bbeb03992e 100644 --- a/osu.Game/Rulesets/Objects/SliderPath.cs +++ b/osu.Game/Rulesets/Objects/SliderPath.cs @@ -37,13 +37,8 @@ namespace osu.Game.Rulesets.Objects /// /// Creates a new . /// - /// An optional set of s to initialise the path with. - /// A user-set distance of the path that may be shorter or longer than the true distance between all control points. - /// The path will be shortened/lengthened to match this length. If null, the path will use the true distance between all control points. - [JsonConstructor] - public SliderPath(PathControlPoint[] controlPoints = null, double? expectedDistance = null) + public SliderPath() { - ExpectedDistance.Value = expectedDistance; ExpectedDistance.ValueChanged += _ => pathCache.Invalidate(); ControlPoints.ItemsAdded += items => @@ -62,11 +57,33 @@ namespace osu.Game.Rulesets.Objects onControlPointChanged(); }; - ControlPoints.AddRange(controlPoints); - void onControlPointChanged() => pathCache.Invalidate(); } + /// + /// Creates a new . + /// + /// An optional set of s to initialise the path with. + /// A user-set distance of the path that may be shorter or longer than the true distance between all control points. + /// The path will be shortened/lengthened to match this length. If null, the path will use the true distance between all control points. + [JsonConstructor] + public SliderPath(PathControlPoint[] controlPoints, double? expectedDistance = null) + : this() + { + ControlPoints.AddRange(controlPoints); + ExpectedDistance.Value = expectedDistance; + } + + public SliderPath(PathType type, Vector2[] controlPoints, double? expectedDistance = null) + : this() + { + foreach (var c in controlPoints) + ControlPoints.Add(new PathControlPoint { Position = { Value = c } }); + ControlPoints[0].Type.Value = type; + + ExpectedDistance.Value = expectedDistance; + } + /// /// The distance of the path after lengthening/shortening to account for . /// From c9a66c0d07a8a89267355133a0eb8caead972c88 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 5 Dec 2019 18:31:28 +0900 Subject: [PATCH 08/89] Expose a version to indicate path changes --- osu.Game/Rulesets/Objects/SliderPath.cs | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/osu.Game/Rulesets/Objects/SliderPath.cs b/osu.Game/Rulesets/Objects/SliderPath.cs index bbeb03992e..6ad5d2f1c3 100644 --- a/osu.Game/Rulesets/Objects/SliderPath.cs +++ b/osu.Game/Rulesets/Objects/SliderPath.cs @@ -16,6 +16,13 @@ namespace osu.Game.Rulesets.Objects { public class SliderPath { + /// + /// The current version of this . Updated when any change to the path occurs. + /// + public IBindable Version => version; + + private readonly Bindable version = new Bindable(); + /// /// The user-set distance of the path. If non-null, will match this value, /// and the path will be shortened/lengthened to match this length. @@ -39,25 +46,23 @@ namespace osu.Game.Rulesets.Objects /// public SliderPath() { - ExpectedDistance.ValueChanged += _ => pathCache.Invalidate(); + ExpectedDistance.ValueChanged += _ => invalidate(); ControlPoints.ItemsAdded += items => { foreach (var c in items) - c.Changed += onControlPointChanged; + c.Changed += invalidate; - onControlPointChanged(); + invalidate(); }; ControlPoints.ItemsRemoved += items => { foreach (var c in items) - c.Changed -= onControlPointChanged; + c.Changed -= invalidate; - onControlPointChanged(); + invalidate(); }; - - void onControlPointChanged() => pathCache.Invalidate(); } /// @@ -141,6 +146,12 @@ namespace osu.Game.Rulesets.Objects return interpolateVertices(indexOfDistance(d), d); } + private void invalidate() + { + pathCache.Invalidate(); + version.Value++; + } + private void ensureValid() { if (pathCache.IsValid) From e225b0032aa092c79322fb948fd7e3adb7aac2b4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 5 Dec 2019 19:31:40 +0900 Subject: [PATCH 09/89] Add basic hitobject display to timeline --- .../Editor/TestSceneEditorComposeTimeline.cs | 16 ++++ .../Compose/Components/Timeline/Timeline.cs | 21 +++-- .../Timeline/TimelineHitObjectDisplay.cs | 85 +++++++++++++++++++ 3 files changed, 115 insertions(+), 7 deletions(-) create mode 100644 osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectDisplay.cs diff --git a/osu.Game.Tests/Visual/Editor/TestSceneEditorComposeTimeline.cs b/osu.Game.Tests/Visual/Editor/TestSceneEditorComposeTimeline.cs index 6e5b3b93e9..b67ea798a0 100644 --- a/osu.Game.Tests/Visual/Editor/TestSceneEditorComposeTimeline.cs +++ b/osu.Game.Tests/Visual/Editor/TestSceneEditorComposeTimeline.cs @@ -13,6 +13,10 @@ using osu.Framework.Graphics.Shapes; using osu.Framework.Timing; using osu.Game.Beatmaps; using osu.Game.Graphics.UserInterface; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Osu; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Screens.Edit; using osu.Game.Screens.Edit.Compose.Components.Timeline; using osuTK; using osuTK.Graphics; @@ -25,11 +29,23 @@ namespace osu.Game.Tests.Visual.Editor public override IReadOnlyList RequiredTypes => new[] { typeof(TimelineArea), + typeof(TimelineHitObjectDisplay), typeof(Timeline), typeof(TimelineButton), typeof(CentreMarker) }; + [Cached(typeof(IEditorBeatmap))] + private readonly EditorBeatmap editorBeatmap; + + public TestSceneEditorComposeTimeline() + { + editorBeatmap = new EditorBeatmap( + (Beatmap) + CreateWorkingBeatmap(new OsuRuleset().RulesetInfo).GetPlayableBeatmap(new OsuRuleset().RulesetInfo, new Mod[] { }) + ); + } + [BackgroundDependencyLoader] private void load(AudioManager audio) { diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs index 748c9e2ba3..1f8c134d84 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs @@ -36,14 +36,21 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline { this.adjustableClock = adjustableClock; - Child = waveform = new WaveformGraph + Children = new Drawable[] { - RelativeSizeAxes = Axes.Both, - Colour = colours.Blue.Opacity(0.2f), - LowColour = colours.BlueLighter, - MidColour = colours.BlueDark, - HighColour = colours.BlueDarker, - Depth = float.MaxValue + waveform = new WaveformGraph + { + RelativeSizeAxes = Axes.Both, + Colour = colours.Blue.Opacity(0.2f), + LowColour = colours.BlueLighter, + MidColour = colours.BlueDark, + HighColour = colours.BlueDarker, + Depth = float.MaxValue + }, + new TimelineHitObjectDisplay + { + RelativeSizeAxes = Axes.Both, + }, }; // We don't want the centre marker to scroll diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectDisplay.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectDisplay.cs new file mode 100644 index 0000000000..8bc0bf1a86 --- /dev/null +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectDisplay.cs @@ -0,0 +1,85 @@ +// 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 osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Types; +using osu.Game.Screens.Edit.Components.Timelines.Summary.Parts; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Screens.Edit.Compose.Components.Timeline +{ + internal class TimelineHitObjectDisplay : TimelinePart + { + [Resolved] + private IEditorBeatmap beatmap { get; set; } + + [BackgroundDependencyLoader] + private void load() + { + foreach (var h in beatmap.HitObjects) + add(h); + + beatmap.HitObjectAdded += add; + beatmap.HitObjectRemoved += remove; + } + + private void remove(HitObject h) + { + foreach (var d in InternalChildren.OfType().Where(c => c.HitObject == h)) + d.Expire(); + } + + private void add(HitObject h) + { + Add(new TimelineHitObjectRepresentation(h)); + } + + private class TimelineHitObjectRepresentation : CompositeDrawable + { + public readonly HitObject HitObject; + + public TimelineHitObjectRepresentation(HitObject hitObject) + { + this.HitObject = hitObject; + Anchor = Anchor.CentreLeft; + Origin = Anchor.CentreLeft; + + Width = (float)(hitObject.GetEndTime() - hitObject.StartTime); + + X = (float)hitObject.StartTime; + + RelativePositionAxes = Axes.X; + RelativeSizeAxes = Axes.X; + + AddInternal(new Circle + { + Size = new Vector2(16), + Anchor = Anchor.CentreLeft, + Origin = Anchor.Centre, + RelativePositionAxes = Axes.X, + AlwaysPresent = true, + Colour = Color4.White, + }); + + if (hitObject is IHasEndTime) + { + AddInternal(new Box + { + Size = new Vector2(1, 5), + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + RelativePositionAxes = Axes.X, + RelativeSizeAxes = Axes.X, + Colour = Color4.White, + }); + } + } + } + } +} From 3ebbf62b2ab11d58fd372a091fe179eb7f827188 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 5 Dec 2019 19:53:31 +0900 Subject: [PATCH 10/89] Initial game-wide update with the new SliderPath --- .../TestSceneSliderSelectionBlueprint.cs | 2 +- .../Components/PathControlPointPiece.cs | 28 +++-------- .../Components/PathControlPointVisualiser.cs | 24 +++++---- .../Sliders/SliderPlacementBlueprint.cs | 18 +++---- .../Sliders/SliderSelectionBlueprint.cs | 25 +++------- osu.Game.Rulesets.Osu/Mods/OsuModHardRock.cs | 7 +-- .../Objects/Drawables/DrawableSlider.cs | 6 +-- .../Objects/Drawables/DrawableSliderHead.cs | 6 +-- .../Objects/Drawables/DrawableSliderTail.cs | 6 +-- osu.Game.Rulesets.Osu/Objects/Slider.cs | 14 +----- .../Objects/SliderTailCircle.cs | 6 +-- .../Legacy/Catch/ConvertHitObjectParser.cs | 4 +- .../Objects/Legacy/ConvertHitObjectParser.cs | 50 +++++++++++++++---- .../Legacy/Mania/ConvertHitObjectParser.cs | 4 +- .../Legacy/Osu/ConvertHitObjectParser.cs | 4 +- .../Legacy/Taiko/ConvertHitObjectParser.cs | 4 +- 16 files changed, 97 insertions(+), 111 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSelectionBlueprint.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSelectionBlueprint.cs index dde2aa53e0..013920684c 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSelectionBlueprint.cs @@ -196,7 +196,7 @@ namespace osu.Game.Rulesets.Osu.Tests { AddStep($"move mouse to control point {index}", () => { - Vector2 position = slider.Position + slider.Path.ControlPoints[index]; + Vector2 position = slider.Position + slider.Path.ControlPoints[index].Position.Value; InputManager.MoveMouseTo(drawableObject.Parent.ToScreenSpace(position)); }); } diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs index 0ccf020300..159916f16f 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs @@ -90,7 +90,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components { base.Update(); - Position = slider.StackedPosition + slider.Path.ControlPoints[Index]; + Position = slider.StackedPosition + slider.Path.ControlPoints[Index].Position.Value; updateMarkerDisplay(); updateConnectingPath(); @@ -116,10 +116,10 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components { path.ClearVertices(); - if (Index != slider.Path.ControlPoints.Length - 1) + if (Index != slider.Path.ControlPoints.Count - 1) { path.AddVertex(Vector2.Zero); - path.AddVertex(slider.Path.ControlPoints[Index + 1] - slider.Path.ControlPoints[Index]); + path.AddVertex(slider.Path.ControlPoints[Index + 1].Position.Value - slider.Path.ControlPoints[Index].Position.Value); } path.OriginPosition = path.PositionInBoundingBox(Vector2.Zero); @@ -156,8 +156,6 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components protected override bool OnDrag(DragEvent e) { - var newControlPoints = slider.Path.ControlPoints.ToArray(); - if (Index == 0) { // Special handling for the head control point - the position of the slider changes which means the snapped position and time have to be taken into account @@ -168,29 +166,17 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components slider.StartTime = snappedTime; // Since control points are relative to the position of the slider, they all need to be offset backwards by the delta - for (int i = 1; i < newControlPoints.Length; i++) - newControlPoints[i] -= movementDelta; + for (int i = 1; i < slider.Path.ControlPoints.Count; i++) + slider.Path.ControlPoints[i].Position.Value -= movementDelta; } else - newControlPoints[Index] += e.Delta; - - if (isSegmentSeparatorWithNext) - newControlPoints[Index + 1] = newControlPoints[Index]; - - if (isSegmentSeparatorWithPrevious) - newControlPoints[Index - 1] = newControlPoints[Index]; - - ControlPointsChanged?.Invoke(newControlPoints); + slider.Path.ControlPoints[Index].Position.Value += e.Delta; return true; } protected override bool OnDragEnd(DragEndEvent e) => true; - private bool isSegmentSeparator => isSegmentSeparatorWithNext || isSegmentSeparatorWithPrevious; - - private bool isSegmentSeparatorWithNext => Index < slider.Path.ControlPoints.Length - 1 && slider.Path.ControlPoints[Index + 1] == slider.Path.ControlPoints[Index]; - - private bool isSegmentSeparatorWithPrevious => Index > 0 && slider.Path.ControlPoints[Index - 1] == slider.Path.ControlPoints[Index]; + private bool isSegmentSeparator => slider.Path.ControlPoints[Index].Type.Value.HasValue; } } diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs index cdca48490e..c47e4d7d4a 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs @@ -55,7 +55,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components { base.Update(); - while (slider.Path.ControlPoints.Length > Pieces.Count) + while (slider.Path.ControlPoints.Count > Pieces.Count) { var piece = new PathControlPointPiece(slider, Pieces.Count) { @@ -68,7 +68,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components Pieces.Add(piece); } - while (slider.Path.ControlPoints.Length < Pieces.Count) + while (slider.Path.ControlPoints.Count < Pieces.Count) Pieces.Remove(Pieces[Pieces.Count - 1]); } @@ -105,29 +105,32 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components private bool deleteSelected() { - var newControlPoints = new List(); + int countDeleted = 0; foreach (var piece in Pieces) { - if (!piece.IsSelected.Value) - newControlPoints.Add(slider.Path.ControlPoints[piece.Index]); + if (piece.IsSelected.Value) + { + slider.Path.ControlPoints.RemoveAt(piece.Index); + countDeleted++; + } } // Ensure that there are any points to be deleted - if (newControlPoints.Count == slider.Path.ControlPoints.Length) + if (countDeleted == 0) return false; // If there are 0 remaining control points, treat the slider as being deleted - if (newControlPoints.Count == 0) + if (slider.Path.ControlPoints.Count == 0) { placementHandler?.Delete(slider); return true; } // Make control points relative - Vector2 first = newControlPoints[0]; - for (int i = 0; i < newControlPoints.Count; i++) - newControlPoints[i] = newControlPoints[i] - first; + Vector2 first = slider.Path.ControlPoints[0].Position.Value; + for (int i = 0; i < slider.Path.ControlPoints.Count; i++) + slider.Path.ControlPoints[i].Position.Value = slider.Path.ControlPoints[i].Position.Value - first; // The slider's position defines the position of the first control point, and all further control points are relative to that point slider.Position += first; @@ -136,7 +139,6 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components foreach (var piece in Pieces) piece.IsSelected.Value = false; - ControlPointsChanged?.Invoke(newControlPoints.ToArray()); 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 9c0afada29..62a22dc858 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs @@ -27,8 +27,6 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders private HitCirclePiece headCirclePiece; private HitCirclePiece tailCirclePiece; - private readonly List segments = new List(); - private Vector2 cursor; private InputManager inputManager; private PlacementState state; @@ -40,7 +38,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders : base(new Objects.Slider()) { RelativeSizeAxes = Axes.Both; - segments.Add(new Segment(Vector2.Zero)); + HitObject.Path.ControlPoints.Add(new PathControlPoint { Position = { Value = Vector2.Zero } }); } [BackgroundDependencyLoader] @@ -74,7 +72,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders case PlacementState.Body: // The given screen-space position may have been externally snapped, but the unsnapped position from the input manager // is used instead since snapping control points doesn't make much sense - cursor = ToLocalSpace(inputManager.CurrentState.Mouse.Position) - HitObject.Position; + HitObject.Path.ControlPoints[HitObject.Path.ControlPoints.Count - 1].Position.Value = ToLocalSpace(inputManager.CurrentState.Mouse.Position) - HitObject.Position; break; } } @@ -91,7 +89,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders switch (e.Button) { case MouseButton.Left: - segments.Last().ControlPoints.Add(cursor); + HitObject.Path.ControlPoints.Add(new PathControlPoint { Position = { Value = HitObject.Path.ControlPoints[HitObject.Path.ControlPoints.Count - 1].Position.Value } }); break; } @@ -110,7 +108,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders protected override bool OnDoubleClick(DoubleClickEvent e) { - segments.Add(new Segment(segments[segments.Count - 1].ControlPoints.Last())); + HitObject.Path.ControlPoints[HitObject.Path.ControlPoints.Count - 2].Type.Value = PathType.Bezier; return true; } @@ -134,12 +132,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders private void updateSlider() { - Vector2[] newControlPoints = segments.SelectMany(s => s.ControlPoints).Concat(cursor.Yield()).ToArray(); - - var unsnappedPath = new SliderPath(newControlPoints.Length > 2 ? PathType.Bezier : PathType.Linear, newControlPoints); - var snappedDistance = composer?.GetSnappedDistanceFromDistance(HitObject.StartTime, (float)unsnappedPath.Distance) ?? (float)unsnappedPath.Distance; - - HitObject.Path = new SliderPath(unsnappedPath.Type, newControlPoints, snappedDistance); + HitObject.Path.ExpectedDistance.Value = null; + HitObject.Path.ExpectedDistance.Value = composer?.GetSnappedDistanceFromDistance(HitObject.StartTime, (float)HitObject.Path.Distance) ?? (float)HitObject.Path.Distance; bodyPiece.UpdateFrom(HitObject); headCirclePiece.UpdateFrom(HitObject.HeadCircle); diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs index 820d6c92d7..ab52c906e8 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs @@ -77,12 +77,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders { Debug.Assert(placementControlPointIndex != null); - Vector2 position = e.MousePosition - HitObject.Position; - - var controlPoints = HitObject.Path.ControlPoints.ToArray(); - controlPoints[placementControlPointIndex.Value] = position; - - onNewControlPoints(controlPoints); + HitObject.Path.ControlPoints[placementControlPointIndex.Value].Position.Value = e.MousePosition - HitObject.Position; return true; } @@ -97,15 +92,12 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders { position -= HitObject.Position; - var controlPoints = new Vector2[HitObject.Path.ControlPoints.Length + 1]; - HitObject.Path.ControlPoints.CopyTo(controlPoints); - int insertionIndex = 0; float minDistance = float.MaxValue; - for (int i = 0; i < controlPoints.Length - 2; i++) + for (int i = 0; i < HitObject.Path.ControlPoints.Count - 2; i++) { - float dist = new Line(controlPoints[i], controlPoints[i + 1]).DistanceToPoint(position); + float dist = new Line(HitObject.Path.ControlPoints[i].Position.Value, HitObject.Path.ControlPoints[i + 1].Position.Value).DistanceToPoint(position); if (dist < minDistance) { @@ -115,20 +107,15 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders } // Move the control points from the insertion index onwards to make room for the insertion - Array.Copy(controlPoints, insertionIndex, controlPoints, insertionIndex + 1, controlPoints.Length - insertionIndex - 1); - controlPoints[insertionIndex] = position; - - onNewControlPoints(controlPoints); + HitObject.Path.ControlPoints.Insert(insertionIndex, new PathControlPoint { Position = { Value = position } }); return insertionIndex; } private void onNewControlPoints(Vector2[] controlPoints) { - var unsnappedPath = new SliderPath(controlPoints.Length > 2 ? PathType.Bezier : PathType.Linear, controlPoints); - var snappedDistance = composer?.GetSnappedDistanceFromDistance(HitObject.StartTime, (float)unsnappedPath.Distance) ?? (float)unsnappedPath.Distance; - - HitObject.Path = new SliderPath(unsnappedPath.Type, controlPoints, snappedDistance); + HitObject.Path.ExpectedDistance.Value = null; + HitObject.Path.ExpectedDistance.Value = composer?.GetSnappedDistanceFromDistance(HitObject.StartTime, (float)HitObject.Path.Distance) ?? (float)HitObject.Path.Distance; UpdateHitObject(); } diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModHardRock.cs b/osu.Game.Rulesets.Osu/Mods/OsuModHardRock.cs index 3d566362ae..bc5f79331f 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModHardRock.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModHardRock.cs @@ -28,11 +28,8 @@ namespace osu.Game.Rulesets.Osu.Mods slider.NestedHitObjects.OfType().ForEach(h => h.Position = new Vector2(h.Position.X, OsuPlayfield.BASE_SIZE.Y - h.Position.Y)); slider.NestedHitObjects.OfType().ForEach(h => h.Position = new Vector2(h.Position.X, OsuPlayfield.BASE_SIZE.Y - h.Position.Y)); - var newControlPoints = new Vector2[slider.Path.ControlPoints.Length]; - for (int i = 0; i < slider.Path.ControlPoints.Length; i++) - newControlPoints[i] = new Vector2(slider.Path.ControlPoints[i].X, -slider.Path.ControlPoints[i].Y); - - slider.Path = new SliderPath(slider.Path.Type, newControlPoints, slider.Path.ExpectedDistance); + foreach (var point in slider.Path.ControlPoints) + point.Position.Value = new Vector2(point.Position.Value.X, -point.Position.Value.Y); } } } diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs index 69189758a6..1e0402d492 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs @@ -37,7 +37,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables private readonly IBindable positionBindable = new Bindable(); private readonly IBindable stackHeightBindable = new Bindable(); private readonly IBindable scaleBindable = new Bindable(); - private readonly IBindable pathBindable = new Bindable(); + private readonly IBindable pathVersion = new Bindable(); [Resolved(CanBeNull = true)] private OsuRulesetConfigManager config { get; set; } @@ -84,9 +84,9 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables positionBindable.BindTo(HitObject.PositionBindable); stackHeightBindable.BindTo(HitObject.StackHeightBindable); scaleBindable.BindTo(HitObject.ScaleBindable); - pathBindable.BindTo(slider.PathBindable); + pathVersion.BindTo(slider.Path.Version); - pathBindable.BindValueChanged(_ => Body.Refresh()); + pathVersion.BindValueChanged(_ => Body.Refresh()); AccentColour.BindValueChanged(colour => { diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs index a10c66d1df..166defbc41 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs @@ -13,7 +13,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables public class DrawableSliderHead : DrawableHitCircle { private readonly IBindable positionBindable = new Bindable(); - private readonly IBindable pathBindable = new Bindable(); + private readonly IBindable pathVersion = new Bindable(); private readonly Slider slider; @@ -27,10 +27,10 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables private void load() { positionBindable.BindTo(HitObject.PositionBindable); - pathBindable.BindTo(slider.PathBindable); + pathVersion.BindTo(slider.Path.Version); positionBindable.BindValueChanged(_ => updatePosition()); - pathBindable.BindValueChanged(_ => updatePosition(), true); + pathVersion.BindValueChanged(_ => updatePosition(), true); } protected override void Update() diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs index 42bf5e4d21..8e2f6ffa66 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs @@ -21,7 +21,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables public bool Tracking { get; set; } private readonly IBindable positionBindable = new Bindable(); - private readonly IBindable pathBindable = new Bindable(); + private readonly IBindable pathVersion = new Bindable(); public DrawableSliderTail(Slider slider, SliderTailCircle hitCircle) : base(hitCircle) @@ -36,10 +36,10 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables AlwaysPresent = true; positionBindable.BindTo(hitCircle.PositionBindable); - pathBindable.BindTo(slider.PathBindable); + pathVersion.BindTo(slider.Path.Version); positionBindable.BindValueChanged(_ => updatePosition()); - pathBindable.BindValueChanged(_ => updatePosition(), true); + pathVersion.BindValueChanged(_ => updatePosition(), true); // TODO: This has no drawable content. Support for skins should be added. } diff --git a/osu.Game.Rulesets.Osu/Objects/Slider.cs b/osu.Game.Rulesets.Osu/Objects/Slider.cs index c6f5a075e0..b68595c67e 100644 --- a/osu.Game.Rulesets.Osu/Objects/Slider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Slider.cs @@ -28,19 +28,7 @@ namespace osu.Game.Rulesets.Osu.Objects public Vector2 StackedPositionAt(double t) => StackedPosition + this.CurvePositionAt(t); - public readonly Bindable PathBindable = new Bindable(); - - public SliderPath Path - { - get => PathBindable.Value; - set - { - PathBindable.Value = value; - endPositionCache.Invalidate(); - - updateNestedPositions(); - } - } + public SliderPath Path { get; set; } = new SliderPath(new[] { new PathControlPoint { Type = { Value = PathType.Bezier } } }); public double Distance => Path.Distance; diff --git a/osu.Game.Rulesets.Osu/Objects/SliderTailCircle.cs b/osu.Game.Rulesets.Osu/Objects/SliderTailCircle.cs index 14c3369967..c17d2275b8 100644 --- a/osu.Game.Rulesets.Osu/Objects/SliderTailCircle.cs +++ b/osu.Game.Rulesets.Osu/Objects/SliderTailCircle.cs @@ -15,12 +15,12 @@ namespace osu.Game.Rulesets.Osu.Objects /// public class SliderTailCircle : SliderCircle { - private readonly IBindable pathBindable = new Bindable(); + private readonly IBindable pathVersion = new Bindable(); public SliderTailCircle(Slider slider) { - pathBindable.BindTo(slider.PathBindable); - pathBindable.BindValueChanged(_ => Position = slider.EndPosition); + pathVersion.BindTo(slider.Path.Version); + pathVersion.BindValueChanged(_ => Position = slider.EndPosition); } public override Judgement CreateJudgement() => new OsuSliderTailJudgement(); diff --git a/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertHitObjectParser.cs index 545cfe07f8..afa1f2996e 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertHitObjectParser.cs @@ -37,7 +37,7 @@ namespace osu.Game.Rulesets.Objects.Legacy.Catch }; } - protected override HitObject CreateSlider(Vector2 position, bool newCombo, int comboOffset, Vector2[] controlPoints, double? length, PathType pathType, int repeatCount, + protected override HitObject CreateSlider(Vector2 position, bool newCombo, int comboOffset, PathControlPoint[] controlPoints, double? length, int repeatCount, List> nodeSamples) { newCombo |= forceNewCombo; @@ -51,7 +51,7 @@ namespace osu.Game.Rulesets.Objects.Legacy.Catch X = position.X, NewCombo = FirstObject || newCombo, ComboOffset = comboOffset, - Path = new SliderPath(pathType, controlPoints, length), + Path = new SliderPath(controlPoints, length), NodeSamples = nodeSamples, RepeatCount = repeatCount }; diff --git a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs index 5348ff1f02..1ac7284772 100644 --- a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs @@ -115,12 +115,6 @@ namespace osu.Game.Rulesets.Objects.Legacy points[pointIndex++] = new Vector2((int)Parsing.ParseDouble(temp[0], Parsing.MAX_COORDINATE_VALUE), (int)Parsing.ParseDouble(temp[1], Parsing.MAX_COORDINATE_VALUE)) - pos; } - // osu-stable special-cased colinear perfect curves to a CurveType.Linear - static bool isLinear(Vector2[] p) => Precision.AlmostEquals(0, (p[1].Y - p[0].Y) * (p[2].X - p[0].X) - (p[1].X - p[0].X) * (p[2].Y - p[0].Y)); - - if (points.Length == 3 && pathType == PathType.PerfectCurve && isLinear(points)) - pathType = PathType.Linear; - int repeatCount = Parsing.ParseInt(split[6]); if (repeatCount > 9000) @@ -187,7 +181,7 @@ namespace osu.Game.Rulesets.Objects.Legacy for (int i = 0; i < nodes; i++) nodeSamples.Add(convertSoundType(nodeSoundTypes[i], nodeBankInfos[i])); - result = CreateSlider(pos, combo, comboOffset, points, length, pathType, repeatCount, nodeSamples); + result = CreateSlider(pos, combo, comboOffset, convertControlPoints(points, pathType), length, repeatCount, nodeSamples); // The samples are played when the slider ends, which is the last node result.Samples = nodeSamples[nodeSamples.Count - 1]; @@ -259,6 +253,45 @@ namespace osu.Game.Rulesets.Objects.Legacy bankInfo.Filename = split.Length > 4 ? split[4] : null; } + private PathControlPoint[] convertControlPoints(Vector2[] vertices, PathType type) + { + if (type == PathType.PerfectCurve) + { + if (vertices.Length == 3) + { + // osu-stable special-cased colinear perfect curves to a linear path + if (isLinear(vertices)) + type = PathType.Linear; + } + else + type = PathType.Bezier; + } + + var points = new List(vertices.Length) + { + new PathControlPoint + { + Position = { Value = vertices[0] }, + Type = { Value = type } + } + }; + + for (int i = 1; i < vertices.Length; i++) + { + if (vertices[i] == vertices[i - 1]) + { + points[points.Count - 1].Type.Value = type; + continue; + } + + points.Add(new PathControlPoint { Position = { Value = vertices[i] } }); + } + + return points.ToArray(); + + static bool isLinear(Vector2[] p) => Precision.AlmostEquals(0, (p[1].Y - p[0].Y) * (p[2].X - p[0].X) - (p[1].X - p[0].X) * (p[2].Y - p[0].Y)); + } + /// /// Creates a legacy Hit-type hit object. /// @@ -276,11 +309,10 @@ namespace osu.Game.Rulesets.Objects.Legacy /// When starting a new combo, the offset of the new combo relative to the current one. /// The slider control points. /// The slider length. - /// The slider curve type. /// The slider repeat count. /// The samples to be played when the slider nodes are hit. This includes the head and tail of the slider. /// The hit object. - protected abstract HitObject CreateSlider(Vector2 position, bool newCombo, int comboOffset, Vector2[] controlPoints, double? length, PathType pathType, int repeatCount, + protected abstract HitObject CreateSlider(Vector2 position, bool newCombo, int comboOffset, PathControlPoint[] controlPoints, double? length, int repeatCount, List> nodeSamples); /// diff --git a/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHitObjectParser.cs index 8012b4230f..d1a8adc2b7 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHitObjectParser.cs @@ -26,13 +26,13 @@ namespace osu.Game.Rulesets.Objects.Legacy.Mania }; } - protected override HitObject CreateSlider(Vector2 position, bool newCombo, int comboOffset, Vector2[] controlPoints, double? length, PathType pathType, int repeatCount, + protected override HitObject CreateSlider(Vector2 position, bool newCombo, int comboOffset, PathControlPoint[] controlPoints, double? length, int repeatCount, List> nodeSamples) { return new ConvertSlider { X = position.X, - Path = new SliderPath(pathType, controlPoints, length), + Path = new SliderPath(controlPoints, length), NodeSamples = nodeSamples, RepeatCount = repeatCount }; diff --git a/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertHitObjectParser.cs index 99872e630d..6628f7d059 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertHitObjectParser.cs @@ -37,7 +37,7 @@ namespace osu.Game.Rulesets.Objects.Legacy.Osu }; } - protected override HitObject CreateSlider(Vector2 position, bool newCombo, int comboOffset, Vector2[] controlPoints, double? length, PathType pathType, int repeatCount, + protected override HitObject CreateSlider(Vector2 position, bool newCombo, int comboOffset, PathControlPoint[] controlPoints, double? length, int repeatCount, List> nodeSamples) { newCombo |= forceNewCombo; @@ -51,7 +51,7 @@ namespace osu.Game.Rulesets.Objects.Legacy.Osu Position = position, NewCombo = FirstObject || newCombo, ComboOffset = comboOffset, - Path = new SliderPath(pathType, controlPoints, length), + Path = new SliderPath(controlPoints, length), NodeSamples = nodeSamples, RepeatCount = repeatCount }; diff --git a/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertHitObjectParser.cs index 9dc0c01932..7b1d64b19f 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertHitObjectParser.cs @@ -23,12 +23,12 @@ namespace osu.Game.Rulesets.Objects.Legacy.Taiko return new ConvertHit(); } - protected override HitObject CreateSlider(Vector2 position, bool newCombo, int comboOffset, Vector2[] controlPoints, double? length, PathType pathType, int repeatCount, + protected override HitObject CreateSlider(Vector2 position, bool newCombo, int comboOffset, PathControlPoint[] controlPoints, double? length, int repeatCount, List> nodeSamples) { return new ConvertSlider { - Path = new SliderPath(pathType, controlPoints, length), + Path = new SliderPath(controlPoints, length), NodeSamples = nodeSamples, RepeatCount = repeatCount }; From d8620a70fbbb7bbc9f983126a98478447fba9dc5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 5 Dec 2019 20:12:25 +0900 Subject: [PATCH 11/89] Make work in editor --- osu.Game/Rulesets/Edit/HitObjectComposer.cs | 8 ++++++-- .../Timelines/Summary/Parts/TimelinePart.cs | 6 +++--- .../Compose/Components/Timeline/Timeline.cs | 4 ---- .../Components/Timeline/TimelineArea.cs | 10 +++++++--- .../Timeline/TimelineHitObjectDisplay.cs | 20 ++++++++++++------- .../Screens/Edit/Compose/ComposeScreen.cs | 13 ++++++++++-- .../Screens/Edit/EditorScreenWithTimeline.cs | 10 ++++++++-- osu.Game/Screens/Edit/IEditorBeatmap.cs | 5 +++++ 8 files changed, 53 insertions(+), 23 deletions(-) diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index 805fc2b46f..368520ba43 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -34,7 +34,9 @@ namespace osu.Game.Rulesets.Edit where TObject : HitObject { protected IRulesetConfigManager Config { get; private set; } - protected EditorBeatmap EditorBeatmap { get; private set; } + + protected new EditorBeatmap EditorBeatmap { get; private set; } + protected readonly Ruleset Ruleset; [Resolved] @@ -148,7 +150,7 @@ namespace osu.Game.Rulesets.Edit beatmapProcessor = Ruleset.CreateBeatmapProcessor(playableBeatmap); - EditorBeatmap = new EditorBeatmap(playableBeatmap); + base.EditorBeatmap = EditorBeatmap = new EditorBeatmap(playableBeatmap); EditorBeatmap.HitObjectAdded += addHitObject; EditorBeatmap.HitObjectRemoved += removeHitObject; EditorBeatmap.StartTimeChanged += UpdateHitObject; @@ -333,6 +335,8 @@ namespace osu.Game.Rulesets.Edit /// public abstract IEnumerable HitObjects { get; } + public IEditorBeatmap EditorBeatmap { get; protected set; } + /// /// Whether the user's cursor is currently in an area of the that is valid for placement. /// diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/TimelinePart.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/TimelinePart.cs index 26d9614631..7706e33179 100644 --- a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/TimelinePart.cs +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/TimelinePart.cs @@ -14,12 +14,14 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts /// /// Represents a part of the summary timeline.. /// - public abstract class TimelinePart : CompositeDrawable + public abstract class TimelinePart : Container { protected readonly IBindable Beatmap = new Bindable(); private readonly Container timeline; + protected override Container Content => timeline; + protected TimelinePart() { AddInternal(timeline = new Container { RelativeSizeAxes = Axes.Both }); @@ -50,8 +52,6 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts timeline.RelativeChildSize = new Vector2((float)Math.Max(1, Beatmap.Value.Track.Length), 1); } - protected void Add(Drawable visualisation) => timeline.Add(visualisation); - protected virtual void LoadBeatmap(WorkingBeatmap beatmap) { timeline.Clear(); diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs index 1f8c134d84..2a565d028f 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs @@ -47,10 +47,6 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline HighColour = colours.BlueDarker, Depth = float.MaxValue }, - new TimelineHitObjectDisplay - { - RelativeSizeAxes = Axes.Both, - }, }; // We don't want the centre marker to scroll diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineArea.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineArea.cs index 863a120fc3..93dac059e3 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineArea.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineArea.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 osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; @@ -11,11 +12,14 @@ using osuTK; namespace osu.Game.Screens.Edit.Compose.Components.Timeline { - public class TimelineArea : CompositeDrawable + public class TimelineArea : Container { - private readonly Timeline timeline; + private Timeline timeline; - public TimelineArea() + protected override Container Content => timeline; + + [BackgroundDependencyLoader] + private void load() { Masking = true; CornerRadius = 5; diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectDisplay.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectDisplay.cs index 8bc0bf1a86..fc16d29639 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectDisplay.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectDisplay.cs @@ -16,8 +16,12 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline { internal class TimelineHitObjectDisplay : TimelinePart { - [Resolved] - private IEditorBeatmap beatmap { get; set; } + private IEditorBeatmap beatmap { get; } + + public TimelineHitObjectDisplay(IEditorBeatmap beatmap) + { + this.beatmap = beatmap; + } [BackgroundDependencyLoader] private void load() @@ -27,18 +31,20 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline beatmap.HitObjectAdded += add; beatmap.HitObjectRemoved += remove; + beatmap.StartTimeChanged += h => + { + remove(h); + add(h); + }; } private void remove(HitObject h) { - foreach (var d in InternalChildren.OfType().Where(c => c.HitObject == h)) + foreach (var d in Children.OfType().Where(c => c.HitObject == h)) d.Expire(); } - private void add(HitObject h) - { - Add(new TimelineHitObjectRepresentation(h)); - } + private void add(HitObject h) => Add(new TimelineHitObjectRepresentation(h)); private class TimelineHitObjectRepresentation : CompositeDrawable { diff --git a/osu.Game/Screens/Edit/Compose/ComposeScreen.cs b/osu.Game/Screens/Edit/Compose/ComposeScreen.cs index 2e9094ebe6..8511bc3242 100644 --- a/osu.Game/Screens/Edit/Compose/ComposeScreen.cs +++ b/osu.Game/Screens/Edit/Compose/ComposeScreen.cs @@ -3,17 +3,21 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Game.Rulesets.Edit; +using osu.Game.Screens.Edit.Compose.Components.Timeline; using osu.Game.Skinning; namespace osu.Game.Screens.Edit.Compose { public class ComposeScreen : EditorScreenWithTimeline { + private HitObjectComposer composer; + protected override Drawable CreateMainContent() { var ruleset = Beatmap.Value.BeatmapInfo.Ruleset?.CreateInstance(); - var composer = ruleset?.CreateHitObjectComposer(); + composer = ruleset?.CreateHitObjectComposer(); if (composer != null) { @@ -25,10 +29,15 @@ namespace osu.Game.Screens.Edit.Compose // load the skinning hierarchy first. // this is intentionally done in two stages to ensure things are in a loaded state before exposing the ruleset to skin sources. - return beatmapSkinProvider.WithChild(rulesetSkinProvider.WithChild(ruleset.CreateHitObjectComposer())); + return beatmapSkinProvider.WithChild(rulesetSkinProvider.WithChild(composer)); } return new ScreenWhiteBox.UnderConstructionMessage(ruleset == null ? "This beatmap" : $"{ruleset.Description}'s composer"); } + + protected override Drawable CreateTimelineContent() => new TimelineHitObjectDisplay(composer.EditorBeatmap) + { + RelativeSizeAxes = Axes.Both, + }; } } diff --git a/osu.Game/Screens/Edit/EditorScreenWithTimeline.cs b/osu.Game/Screens/Edit/EditorScreenWithTimeline.cs index 752356e8c4..1b9d1be4f8 100644 --- a/osu.Game/Screens/Edit/EditorScreenWithTimeline.cs +++ b/osu.Game/Screens/Edit/EditorScreenWithTimeline.cs @@ -20,6 +20,8 @@ namespace osu.Game.Screens.Edit private readonly BindableBeatDivisor beatDivisor = new BindableBeatDivisor(); + private TimelineArea timelineArea; + [BackgroundDependencyLoader(true)] private void load([CanBeNull] BindableBeatDivisor beatDivisor) { @@ -64,7 +66,7 @@ namespace osu.Game.Screens.Edit { RelativeSizeAxes = Axes.Both, Padding = new MarginPadding { Right = 5 }, - Child = CreateTimeline() + Child = timelineArea = CreateTimelineArea() }, new BeatDivisorControl(beatDivisor) { RelativeSizeAxes = Axes.Both } }, @@ -97,11 +99,15 @@ namespace osu.Game.Screens.Edit { mainContent.Add(content); content.FadeInFromZero(300, Easing.OutQuint); + + LoadComponentAsync(CreateTimelineContent(), timelineArea.Add); }); } protected abstract Drawable CreateMainContent(); - protected virtual Drawable CreateTimeline() => new TimelineArea { RelativeSizeAxes = Axes.Both }; + protected virtual Drawable CreateTimelineContent() => new Container { }; + + protected TimelineArea CreateTimelineArea() => new TimelineArea { RelativeSizeAxes = Axes.Both }; } } diff --git a/osu.Game/Screens/Edit/IEditorBeatmap.cs b/osu.Game/Screens/Edit/IEditorBeatmap.cs index 2f250ba446..3e3418ef79 100644 --- a/osu.Game/Screens/Edit/IEditorBeatmap.cs +++ b/osu.Game/Screens/Edit/IEditorBeatmap.cs @@ -23,6 +23,11 @@ namespace osu.Game.Screens.Edit /// Invoked when a is removed from this . /// event Action HitObjectRemoved; + + /// + /// Invoked when the start time of a in this was changed. + /// + event Action StartTimeChanged; } /// From e76f8bdd6414542de669ea0301bebf921cf0be06 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 5 Dec 2019 23:31:21 +0900 Subject: [PATCH 12/89] Fix warnings --- .../Editor/TestSceneEditorComposeTimeline.cs | 16 +++---- .../Timeline/TimelineHitObjectDisplay.cs | 43 ++++++++++++------- .../Screens/Edit/EditorScreenWithTimeline.cs | 2 +- 3 files changed, 34 insertions(+), 27 deletions(-) diff --git a/osu.Game.Tests/Visual/Editor/TestSceneEditorComposeTimeline.cs b/osu.Game.Tests/Visual/Editor/TestSceneEditorComposeTimeline.cs index b67ea798a0..2b46418659 100644 --- a/osu.Game.Tests/Visual/Editor/TestSceneEditorComposeTimeline.cs +++ b/osu.Game.Tests/Visual/Editor/TestSceneEditorComposeTimeline.cs @@ -35,20 +35,14 @@ namespace osu.Game.Tests.Visual.Editor typeof(CentreMarker) }; - [Cached(typeof(IEditorBeatmap))] - private readonly EditorBeatmap editorBeatmap; - - public TestSceneEditorComposeTimeline() - { - editorBeatmap = new EditorBeatmap( - (Beatmap) - CreateWorkingBeatmap(new OsuRuleset().RulesetInfo).GetPlayableBeatmap(new OsuRuleset().RulesetInfo, new Mod[] { }) - ); - } - [BackgroundDependencyLoader] private void load(AudioManager audio) { + var editorBeatmap = new EditorBeatmap( + (Beatmap) + CreateWorkingBeatmap(new OsuRuleset().RulesetInfo).GetPlayableBeatmap(new OsuRuleset().RulesetInfo, new Mod[] { }) + ); + Beatmap.Value = new WaveformTestBeatmap(audio); Children = new Drawable[] diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectDisplay.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectDisplay.cs index fc16d29639..337fd9a6b2 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectDisplay.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectDisplay.cs @@ -44,7 +44,12 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline d.Expire(); } - private void add(HitObject h) => Add(new TimelineHitObjectRepresentation(h)); + private void add(HitObject h) + { + var yOffset = Children.Count(d => d.X == h.StartTime); + + Add(new TimelineHitObjectRepresentation(h) { Y = -yOffset * 4 }); + } private class TimelineHitObjectRepresentation : CompositeDrawable { @@ -52,7 +57,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline public TimelineHitObjectRepresentation(HitObject hitObject) { - this.HitObject = hitObject; + HitObject = hitObject; Anchor = Anchor.CentreLeft; Origin = Anchor.CentreLeft; @@ -63,6 +68,25 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline RelativePositionAxes = Axes.X; RelativeSizeAxes = Axes.X; + if (hitObject is IHasEndTime) + { + AddInternal(new Container + { + CornerRadius = 2, + Masking = true, + Size = new Vector2(1, 3), + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + RelativePositionAxes = Axes.X, + RelativeSizeAxes = Axes.X, + Colour = Color4.Black, + Child = new Box + { + RelativeSizeAxes = Axes.Both, + } + }); + } + AddInternal(new Circle { Size = new Vector2(16), @@ -71,20 +95,9 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline RelativePositionAxes = Axes.X, AlwaysPresent = true, Colour = Color4.White, + BorderColour = Color4.Black, + BorderThickness = 3, }); - - if (hitObject is IHasEndTime) - { - AddInternal(new Box - { - Size = new Vector2(1, 5), - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - RelativePositionAxes = Axes.X, - RelativeSizeAxes = Axes.X, - Colour = Color4.White, - }); - } } } } diff --git a/osu.Game/Screens/Edit/EditorScreenWithTimeline.cs b/osu.Game/Screens/Edit/EditorScreenWithTimeline.cs index 1b9d1be4f8..aa8d99b517 100644 --- a/osu.Game/Screens/Edit/EditorScreenWithTimeline.cs +++ b/osu.Game/Screens/Edit/EditorScreenWithTimeline.cs @@ -106,7 +106,7 @@ namespace osu.Game.Screens.Edit protected abstract Drawable CreateMainContent(); - protected virtual Drawable CreateTimelineContent() => new Container { }; + protected virtual Drawable CreateTimelineContent() => new Container(); protected TimelineArea CreateTimelineArea() => new TimelineArea { RelativeSizeAxes = Axes.Both }; } From 12a98438353fbec5966e234dad10ab8d7f4a1dc9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 6 Dec 2019 10:48:18 +0900 Subject: [PATCH 13/89] Move thickness to a constant --- .../Components/Timeline/TimelineHitObjectDisplay.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectDisplay.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectDisplay.cs index 337fd9a6b2..7d6d8fd729 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectDisplay.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectDisplay.cs @@ -48,11 +48,13 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline { var yOffset = Children.Count(d => d.X == h.StartTime); - Add(new TimelineHitObjectRepresentation(h) { Y = -yOffset * 4 }); + Add(new TimelineHitObjectRepresentation(h) { Y = -yOffset * TimelineHitObjectRepresentation.THICKNESS }); } private class TimelineHitObjectRepresentation : CompositeDrawable { + public const float THICKNESS = 3; + public readonly HitObject HitObject; public TimelineHitObjectRepresentation(HitObject hitObject) @@ -74,7 +76,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline { CornerRadius = 2, Masking = true, - Size = new Vector2(1, 3), + Size = new Vector2(1, THICKNESS), Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, RelativePositionAxes = Axes.X, @@ -96,7 +98,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline AlwaysPresent = true, Colour = Color4.White, BorderColour = Color4.Black, - BorderThickness = 3, + BorderThickness = THICKNESS, }); } } From 28400aa865d4727eb885d50ffe0b0a1a553cff6c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 6 Dec 2019 11:26:50 +0900 Subject: [PATCH 14/89] Update test scene --- .../Editor/TestSceneEditorComposeTimeline.cs | 12 ++++-------- .../Compose/Components/Timeline/Timeline.cs | 19 ++++++++----------- .../Components/Timeline/TimelineArea.cs | 4 ++-- .../Timeline/TimelineHitObjectDisplay.cs | 2 ++ .../Screens/Edit/Compose/ComposeScreen.cs | 5 +---- 5 files changed, 17 insertions(+), 25 deletions(-) diff --git a/osu.Game.Tests/Visual/Editor/TestSceneEditorComposeTimeline.cs b/osu.Game.Tests/Visual/Editor/TestSceneEditorComposeTimeline.cs index 2b46418659..e618256c03 100644 --- a/osu.Game.Tests/Visual/Editor/TestSceneEditorComposeTimeline.cs +++ b/osu.Game.Tests/Visual/Editor/TestSceneEditorComposeTimeline.cs @@ -13,9 +13,7 @@ using osu.Framework.Graphics.Shapes; using osu.Framework.Timing; using osu.Game.Beatmaps; using osu.Game.Graphics.UserInterface; -using osu.Game.Rulesets.Mods; -using osu.Game.Rulesets.Osu; -using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Objects; using osu.Game.Screens.Edit; using osu.Game.Screens.Edit.Compose.Components.Timeline; using osuTK; @@ -38,13 +36,10 @@ namespace osu.Game.Tests.Visual.Editor [BackgroundDependencyLoader] private void load(AudioManager audio) { - var editorBeatmap = new EditorBeatmap( - (Beatmap) - CreateWorkingBeatmap(new OsuRuleset().RulesetInfo).GetPlayableBeatmap(new OsuRuleset().RulesetInfo, new Mod[] { }) - ); - Beatmap.Value = new WaveformTestBeatmap(audio); + var editorBeatmap = new EditorBeatmap((Beatmap)Beatmap.Value.Beatmap); + Children = new Drawable[] { new FillFlowContainer @@ -60,6 +55,7 @@ namespace osu.Game.Tests.Visual.Editor }, new TimelineArea { + Child = new TimelineHitObjectDisplay(editorBeatmap), Anchor = Anchor.Centre, Origin = Anchor.Centre, RelativeSizeAxes = Axes.X, diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs index 2a565d028f..b4f3b1f610 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs @@ -36,18 +36,15 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline { this.adjustableClock = adjustableClock; - Children = new Drawable[] + Add(waveform = new WaveformGraph { - waveform = new WaveformGraph - { - RelativeSizeAxes = Axes.Both, - Colour = colours.Blue.Opacity(0.2f), - LowColour = colours.BlueLighter, - MidColour = colours.BlueDark, - HighColour = colours.BlueDarker, - Depth = float.MaxValue - }, - }; + RelativeSizeAxes = Axes.Both, + Colour = colours.Blue.Opacity(0.2f), + LowColour = colours.BlueLighter, + MidColour = colours.BlueDark, + HighColour = colours.BlueDarker, + Depth = float.MaxValue + }); // We don't want the centre marker to scroll AddInternal(new CentreMarker()); diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineArea.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineArea.cs index 93dac059e3..8909b3d422 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineArea.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineArea.cs @@ -14,7 +14,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline { public class TimelineArea : Container { - private Timeline timeline; + private readonly Timeline timeline = new Timeline { RelativeSizeAxes = Axes.Both }; protected override Container Content => timeline; @@ -111,7 +111,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline } } }, - timeline = new Timeline { RelativeSizeAxes = Axes.Both } + timeline }, }, ColumnDimensions = new[] diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectDisplay.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectDisplay.cs index 7d6d8fd729..db4aca75e5 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectDisplay.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectDisplay.cs @@ -20,6 +20,8 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline public TimelineHitObjectDisplay(IEditorBeatmap beatmap) { + RelativeSizeAxes = Axes.Both; + this.beatmap = beatmap; } diff --git a/osu.Game/Screens/Edit/Compose/ComposeScreen.cs b/osu.Game/Screens/Edit/Compose/ComposeScreen.cs index 8511bc3242..cb8f2b2caa 100644 --- a/osu.Game/Screens/Edit/Compose/ComposeScreen.cs +++ b/osu.Game/Screens/Edit/Compose/ComposeScreen.cs @@ -35,9 +35,6 @@ namespace osu.Game.Screens.Edit.Compose return new ScreenWhiteBox.UnderConstructionMessage(ruleset == null ? "This beatmap" : $"{ruleset.Description}'s composer"); } - protected override Drawable CreateTimelineContent() => new TimelineHitObjectDisplay(composer.EditorBeatmap) - { - RelativeSizeAxes = Axes.Both, - }; + protected override Drawable CreateTimelineContent() => new TimelineHitObjectDisplay(composer.EditorBeatmap); } } From 23c7132c4fdc452ab2e3b1b7d60fd179f0ea4082 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 6 Dec 2019 11:53:22 +0900 Subject: [PATCH 15/89] Add missing license header --- osu.Game/Rulesets/Objects/PathControlPoint.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Rulesets/Objects/PathControlPoint.cs b/osu.Game/Rulesets/Objects/PathControlPoint.cs index d68ef4112b..83436b7a36 100644 --- a/osu.Game/Rulesets/Objects/PathControlPoint.cs +++ b/osu.Game/Rulesets/Objects/PathControlPoint.cs @@ -1,3 +1,6 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + using System; using osu.Framework.Bindables; using osu.Game.Rulesets.Objects.Types; From 9f4c8db39508f52fe377d07d58cee38682dc1087 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 6 Dec 2019 12:17:41 +0900 Subject: [PATCH 16/89] Add xmldoc --- osu.Game/Rulesets/Edit/HitObjectComposer.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index 368520ba43..9ac967ef74 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -335,6 +335,9 @@ namespace osu.Game.Rulesets.Edit /// public abstract IEnumerable HitObjects { get; } + /// + /// An editor-specific beatmap, exposing mutation events. + /// public IEditorBeatmap EditorBeatmap { get; protected set; } /// From 9248fbe881f81a0d0d0516ad1e770179fb71a5d0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 6 Dec 2019 12:22:44 +0900 Subject: [PATCH 17/89] Remove extra checkboxes for now --- .../Edit/Compose/Components/Timeline/TimelineArea.cs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineArea.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineArea.cs index 8909b3d422..02e5db306d 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineArea.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineArea.cs @@ -24,8 +24,6 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline Masking = true; CornerRadius = 5; - OsuCheckbox hitObjectsCheckbox; - OsuCheckbox hitSoundsCheckbox; OsuCheckbox waveformCheckbox; InternalChildren = new Drawable[] @@ -64,8 +62,6 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline Spacing = new Vector2(0, 4), Children = new[] { - hitObjectsCheckbox = new OsuCheckbox { LabelText = "Hit objects" }, - hitSoundsCheckbox = new OsuCheckbox { LabelText = "Hit sounds" }, waveformCheckbox = new OsuCheckbox { LabelText = "Waveform" } } } @@ -123,8 +119,6 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline } }; - hitObjectsCheckbox.Current.Value = true; - hitSoundsCheckbox.Current.Value = true; waveformCheckbox.Current.Value = true; timeline.WaveformVisible.BindTo(waveformCheckbox.Current); From 247609388ff71a2321baae9171bd29d364c3b397 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 6 Dec 2019 12:31:22 +0900 Subject: [PATCH 18/89] Clean up unused/unnecessary properties --- .../Blueprints/Sliders/Components/PathControlPointPiece.cs | 5 +---- .../Sliders/Components/PathControlPointVisualiser.cs | 7 +------ .../Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs | 2 +- .../Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs | 2 +- 4 files changed, 4 insertions(+), 12 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs index 159916f16f..4fe02135c4 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs @@ -21,7 +21,6 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components public class PathControlPointPiece : BlueprintPiece { public Action RequestSelection; - public Action ControlPointsChanged; public readonly BindableBool IsSelected = new BindableBool(); public readonly int Index; @@ -103,7 +102,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components { markerRing.Alpha = IsSelected.Value ? 1 : 0; - Color4 colour = isSegmentSeparator ? colours.Red : colours.Yellow; + Color4 colour = slider.Path.ControlPoints[Index].Type.Value.HasValue ? colours.Red : colours.Yellow; if (IsHovered || IsSelected.Value) colour = Color4.White; marker.Colour = colour; @@ -176,7 +175,5 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components } protected override bool OnDragEnd(DragEndEvent e) => true; - - private bool isSegmentSeparator => slider.Path.ControlPoints[Index].Type.Value.HasValue; } } diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs index c47e4d7d4a..d599ebd893 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs @@ -23,8 +23,6 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components { public class PathControlPointVisualiser : CompositeDrawable, IKeyBindingHandler, IHasContextMenu { - public Action ControlPointsChanged; - internal readonly Container Pieces; private readonly Slider slider; private readonly bool allowSelection; @@ -57,10 +55,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components while (slider.Path.ControlPoints.Count > Pieces.Count) { - var piece = new PathControlPointPiece(slider, Pieces.Count) - { - ControlPointsChanged = c => ControlPointsChanged?.Invoke(c), - }; + var piece = new PathControlPointPiece(slider, Pieces.Count); if (allowSelection) piece.RequestSelection = selectPiece; diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs index 62a22dc858..341db249cc 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs @@ -49,7 +49,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders bodyPiece = new SliderBodyPiece(), headCirclePiece = new HitCirclePiece(), tailCirclePiece = new HitCirclePiece(), - new PathControlPointVisualiser(HitObject, false) { ControlPointsChanged = _ => updateSlider() }, + new PathControlPointVisualiser(HitObject, false) }; setState(PlacementState.Initial); diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs index ab52c906e8..8388d78c42 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs @@ -40,7 +40,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders BodyPiece = new SliderBodyPiece(), HeadBlueprint = CreateCircleSelectionBlueprint(slider, SliderPosition.Start), TailBlueprint = CreateCircleSelectionBlueprint(slider, SliderPosition.End), - ControlPointVisualiser = new PathControlPointVisualiser(sliderObject, true) { ControlPointsChanged = onNewControlPoints }, + ControlPointVisualiser = new PathControlPointVisualiser(sliderObject, true) }; } From b1426d1b22fe9789dd99df497ae2f315b1ac2c2a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 6 Dec 2019 12:51:43 +0900 Subject: [PATCH 19/89] Full impossible nullref --- .../Screens/Edit/Compose/ComposeScreen.cs | 21 ++++++++----------- 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/ComposeScreen.cs b/osu.Game/Screens/Edit/Compose/ComposeScreen.cs index cb8f2b2caa..6984716a2c 100644 --- a/osu.Game/Screens/Edit/Compose/ComposeScreen.cs +++ b/osu.Game/Screens/Edit/Compose/ComposeScreen.cs @@ -16,23 +16,20 @@ namespace osu.Game.Screens.Edit.Compose protected override Drawable CreateMainContent() { var ruleset = Beatmap.Value.BeatmapInfo.Ruleset?.CreateInstance(); - composer = ruleset?.CreateHitObjectComposer(); - if (composer != null) - { - var beatmapSkinProvider = new BeatmapSkinProvidingContainer(Beatmap.Value.Skin); + if (ruleset == null || composer == null) + return new ScreenWhiteBox.UnderConstructionMessage(ruleset == null ? "This beatmap" : $"{ruleset.Description}'s composer"); - // the beatmapSkinProvider is used as the fallback source here to allow the ruleset-specific skin implementation - // full access to all skin sources. - var rulesetSkinProvider = new SkinProvidingContainer(ruleset.CreateLegacySkinProvider(beatmapSkinProvider)); + var beatmapSkinProvider = new BeatmapSkinProvidingContainer(Beatmap.Value.Skin); - // load the skinning hierarchy first. - // this is intentionally done in two stages to ensure things are in a loaded state before exposing the ruleset to skin sources. - return beatmapSkinProvider.WithChild(rulesetSkinProvider.WithChild(composer)); - } + // the beatmapSkinProvider is used as the fallback source here to allow the ruleset-specific skin implementation + // full access to all skin sources. + var rulesetSkinProvider = new SkinProvidingContainer(ruleset.CreateLegacySkinProvider(beatmapSkinProvider)); - return new ScreenWhiteBox.UnderConstructionMessage(ruleset == null ? "This beatmap" : $"{ruleset.Description}'s composer"); + // load the skinning hierarchy first. + // this is intentionally done in two stages to ensure things are in a loaded state before exposing the ruleset to skin sources. + return beatmapSkinProvider.WithChild(rulesetSkinProvider.WithChild(composer)); } protected override Drawable CreateTimelineContent() => new TimelineHitObjectDisplay(composer.EditorBeatmap); From a89a23fe08ea1ec85749a665717c94c6d7b7cc9b Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 6 Dec 2019 13:08:28 +0900 Subject: [PATCH 20/89] Use linq to simplify some expressions --- .../Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs index 341db249cc..6f53a9e4b1 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs @@ -72,7 +72,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders case PlacementState.Body: // The given screen-space position may have been externally snapped, but the unsnapped position from the input manager // is used instead since snapping control points doesn't make much sense - HitObject.Path.ControlPoints[HitObject.Path.ControlPoints.Count - 1].Position.Value = ToLocalSpace(inputManager.CurrentState.Mouse.Position) - HitObject.Position; + HitObject.Path.ControlPoints.Last().Position.Value = ToLocalSpace(inputManager.CurrentState.Mouse.Position) - HitObject.Position; break; } } @@ -89,7 +89,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders switch (e.Button) { case MouseButton.Left: - HitObject.Path.ControlPoints.Add(new PathControlPoint { Position = { Value = HitObject.Path.ControlPoints[HitObject.Path.ControlPoints.Count - 1].Position.Value } }); + HitObject.Path.ControlPoints.Add(new PathControlPoint { Position = { Value = HitObject.Path.ControlPoints.Last().Position.Value } }); break; } @@ -108,6 +108,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders protected override bool OnDoubleClick(DoubleClickEvent e) { + // At the point of a double click, there's guaranteed to be at least two points - one from the click, and one from the cursor HitObject.Path.ControlPoints[HitObject.Path.ControlPoints.Count - 2].Type.Value = PathType.Bezier; return true; } From de23364608a8f6568f7bb1c46080c8c6ef6fee60 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 6 Dec 2019 13:47:34 +0900 Subject: [PATCH 21/89] Add failing test --- .../Visual/Gameplay/TestScenePlayerLoader.cs | 26 +++++++++++++++++++ osu.Game/Screens/Play/PlayerLoader.cs | 10 ++++--- 2 files changed, 32 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs index 74ae641bfe..5142b98898 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.Linq; using System.Threading; +using System.Threading.Tasks; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Audio; @@ -19,6 +20,7 @@ using osu.Game.Overlays; using osu.Game.Overlays.Notifications; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu; +using osu.Game.Rulesets.Osu.Mods; using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; using osu.Game.Screens; @@ -55,6 +57,9 @@ namespace osu.Game.Tests.Visual.Gameplay beforeLoadAction?.Invoke(); Beatmap.Value = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo); + foreach (var mod in Mods.Value.OfType()) + mod.ApplyToClock(Beatmap.Value.Track); + InputManager.Child = container = new TestPlayerLoaderContainer( loader = new TestPlayerLoader(() => { @@ -63,6 +68,25 @@ namespace osu.Game.Tests.Visual.Gameplay })); } + /// + /// When exits early, it has to wait for the player load task + /// to complete before running disposal on player. This previously caused an issue where mod + /// speed adjustments were undone too late, causing cross-screen pollution. + /// + [Test] + public void TestEarlyExit() + { + AddStep("apply mods", () => Mods.Value = new[] { new OsuModNightcore() }); + AddStep("load dummy beatmap", () => ResetPlayer(false)); + AddUntilStep("wait for current", () => loader.IsCurrentScreen()); + AddAssert("mod rate applied", () => Beatmap.Value.Track.Rate != 1); + AddStep("exit loader", () => loader.Exit()); + AddUntilStep("wait for not current", () => !loader.IsCurrentScreen()); + AddAssert("player did not load", () => !player.IsLoaded); + AddUntilStep("player disposed", () => loader.DisposalTask?.IsCompleted == true); + AddAssert("mod rate still applied", () => Beatmap.Value.Track.Rate != 1); + } + [Test] public void TestBlockLoadViaMouseMovement() { @@ -196,6 +220,8 @@ namespace osu.Game.Tests.Visual.Gameplay { public new VisualSettings VisualSettings => base.VisualSettings; + public new Task DisposalTask => base.DisposalTask; + public TestPlayerLoader(Func createPlayer) : base(createPlayer) { diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs index 87d902b547..774ca02be1 100644 --- a/osu.Game/Screens/Play/PlayerLoader.cs +++ b/osu.Game/Screens/Play/PlayerLoader.cs @@ -55,7 +55,9 @@ namespace osu.Game.Screens.Play protected override bool PlayResumeSound => false; - private Task loadTask; + protected Task LoadTask; + + protected Task DisposalTask; private InputManager inputManager; private IdleTracker idleTracker; @@ -159,7 +161,7 @@ namespace osu.Game.Screens.Play player.RestartCount = restartCount; player.RestartRequested = restartRequested; - loadTask = LoadComponentAsync(player, _ => info.Loading = false); + LoadTask = LoadComponentAsync(player, _ => info.Loading = false); } private void contentIn() @@ -250,7 +252,7 @@ namespace osu.Game.Screens.Play { if (!this.IsCurrentScreen()) return; - loadTask = null; + LoadTask = null; //By default, we want to load the player and never be returned to. //Note that this may change if the player we load requested a re-run. @@ -301,7 +303,7 @@ namespace osu.Game.Screens.Play if (isDisposing) { // if the player never got pushed, we should explicitly dispose it. - loadTask?.ContinueWith(_ => player.Dispose()); + DisposalTask = LoadTask?.ContinueWith(_ => player.Dispose()); } } From 48c6279e8bb9d9544afae76e9cb5d1598b62627b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 6 Dec 2019 13:49:01 +0900 Subject: [PATCH 22/89] Only undo adjustments in GameplayClockContainer if applied at least once --- osu.Game/Screens/Play/GameplayClockContainer.cs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/GameplayClockContainer.cs b/osu.Game/Screens/Play/GameplayClockContainer.cs index ff78d85bf0..58c9a6a784 100644 --- a/osu.Game/Screens/Play/GameplayClockContainer.cs +++ b/osu.Game/Screens/Play/GameplayClockContainer.cs @@ -214,10 +214,13 @@ namespace osu.Game.Screens.Play base.Update(); } + private bool speedAdjustmentsApplied; + private void updateRate() { if (sourceClock == null) return; + speedAdjustmentsApplied = true; sourceClock.ResetSpeedAdjustments(); if (sourceClock is IHasTempoAdjust tempo) @@ -239,7 +242,12 @@ namespace osu.Game.Screens.Play private void removeSourceClockAdjustments() { - sourceClock.ResetSpeedAdjustments(); + if (speedAdjustmentsApplied) + { + sourceClock.ResetSpeedAdjustments(); + speedAdjustmentsApplied = false; + } + (sourceClock as IAdjustableAudioComponent)?.RemoveAdjustment(AdjustableProperty.Frequency, pauseFreqAdjust); } } From 21ceb7f85dbbffb771c443f394a98385a3b75b87 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 6 Dec 2019 14:40:45 +0900 Subject: [PATCH 23/89] Always display skins at native sizes for now --- osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs | 4 +++- osu.Game/Skinning/SkinnableDrawable.cs | 4 ++-- osu.Game/Skinning/SkinnableSprite.cs | 2 +- osu.Game/Skinning/SkinnableSpriteText.cs | 2 +- 4 files changed, 7 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs b/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs index 479c250eab..ec9792d59d 100644 --- a/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs @@ -49,7 +49,9 @@ namespace osu.Game.Rulesets.Osu.Skinning return this.GetAnimation(component.LookupName, true, false); case OsuSkinComponents.SliderFollowCircle: - return this.GetAnimation("sliderfollowcircle", true, true); + var followCircle = this.GetAnimation("sliderfollowcircle", true, true); + followCircle.Scale *= 0.5f; + return followCircle; case OsuSkinComponents.SliderBall: var sliderBallContent = this.GetAnimation("sliderb", true, true, ""); diff --git a/osu.Game/Skinning/SkinnableDrawable.cs b/osu.Game/Skinning/SkinnableDrawable.cs index 9ca5d60cb0..fda031e6cb 100644 --- a/osu.Game/Skinning/SkinnableDrawable.cs +++ b/osu.Game/Skinning/SkinnableDrawable.cs @@ -29,13 +29,13 @@ namespace osu.Game.Skinning /// A function to create the default skin implementation of this element. /// A conditional to decide whether to allow fallback to the default implementation if a skinned element is not present. /// How (if at all) the should be resize to fit within our own bounds. - public SkinnableDrawable(ISkinComponent component, Func defaultImplementation, Func allowFallback = null, ConfineMode confineMode = ConfineMode.ScaleDownToFit) + public SkinnableDrawable(ISkinComponent component, Func defaultImplementation, Func allowFallback = null, ConfineMode confineMode = ConfineMode.NoScaling) : this(component, allowFallback, confineMode) { createDefault = defaultImplementation; } - protected SkinnableDrawable(ISkinComponent component, Func allowFallback = null, ConfineMode confineMode = ConfineMode.ScaleDownToFit) + protected SkinnableDrawable(ISkinComponent component, Func allowFallback = null, ConfineMode confineMode = ConfineMode.NoScaling) : base(allowFallback) { this.component = component; diff --git a/osu.Game/Skinning/SkinnableSprite.cs b/osu.Game/Skinning/SkinnableSprite.cs index e225bfc490..5352928ec6 100644 --- a/osu.Game/Skinning/SkinnableSprite.cs +++ b/osu.Game/Skinning/SkinnableSprite.cs @@ -19,7 +19,7 @@ namespace osu.Game.Skinning [Resolved] private TextureStore textures { get; set; } - public SkinnableSprite(string textureName, Func allowFallback = null, ConfineMode confineMode = ConfineMode.ScaleDownToFit) + public SkinnableSprite(string textureName, Func allowFallback = null, ConfineMode confineMode = ConfineMode.NoScaling) : base(new SpriteComponent(textureName), allowFallback, confineMode) { } diff --git a/osu.Game/Skinning/SkinnableSpriteText.cs b/osu.Game/Skinning/SkinnableSpriteText.cs index e72f9c9811..567dd348e1 100644 --- a/osu.Game/Skinning/SkinnableSpriteText.cs +++ b/osu.Game/Skinning/SkinnableSpriteText.cs @@ -8,7 +8,7 @@ namespace osu.Game.Skinning { public class SkinnableSpriteText : SkinnableDrawable, IHasText { - public SkinnableSpriteText(ISkinComponent component, Func defaultImplementation, Func allowFallback = null, ConfineMode confineMode = ConfineMode.ScaleDownToFit) + public SkinnableSpriteText(ISkinComponent component, Func defaultImplementation, Func allowFallback = null, ConfineMode confineMode = ConfineMode.NoScaling) : base(component, defaultImplementation, allowFallback, confineMode) { } From 27dd12a66d885d12b74ddc1a5e0ead595b2f0d30 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 6 Dec 2019 15:06:31 +0900 Subject: [PATCH 24/89] Rewrite slider length calculation for readability --- osu.Game/Rulesets/Objects/SliderPath.cs | 63 +++++++++++++------------ 1 file changed, 33 insertions(+), 30 deletions(-) diff --git a/osu.Game/Rulesets/Objects/SliderPath.cs b/osu.Game/Rulesets/Objects/SliderPath.cs index 6ad5d2f1c3..6c24ee6878 100644 --- a/osu.Game/Rulesets/Objects/SliderPath.cs +++ b/osu.Game/Rulesets/Objects/SliderPath.cs @@ -40,6 +40,7 @@ namespace osu.Game.Rulesets.Objects private readonly List calculatedPath = new List(); private readonly List cumulativeLength = new List(); + private double calculatedLength; /// /// Creates a new . @@ -158,7 +159,7 @@ namespace osu.Game.Rulesets.Objects return; calculatePath(); - calculateCumulativeLength(); + calculateLength(); pathCache.Validate(); } @@ -227,46 +228,48 @@ namespace osu.Game.Rulesets.Objects } } - private void calculateCumulativeLength() + private void calculateLength() { - double l = 0; - + calculatedLength = 0; cumulativeLength.Clear(); - cumulativeLength.Add(l); + cumulativeLength.Add(0); - double? expectedDistance = ExpectedDistance.Value; - - for (int i = 0; i < calculatedPath.Count - 1; ++i) + for (int i = 0; i < calculatedPath.Count - 1; i++) { Vector2 diff = calculatedPath[i + 1] - calculatedPath[i]; - double d = diff.Length; - - // Shorted slider paths that are too long compared to the expected distance - if (expectedDistance.HasValue && expectedDistance - l < d) - { - calculatedPath[i + 1] = calculatedPath[i] + diff * (float)((expectedDistance - l) / d); - calculatedPath.RemoveRange(i + 2, calculatedPath.Count - 2 - i); - - l = expectedDistance.Value; - cumulativeLength.Add(l); - break; - } - - l += d; - cumulativeLength.Add(l); + calculatedLength += diff.Length; + cumulativeLength.Add(calculatedLength); } - // Lengthen slider paths that are too short compared to the expected distance - if (expectedDistance.HasValue && l < expectedDistance && calculatedPath.Count > 1) + if (ExpectedDistance.Value is double expectedDistance && calculatedLength != expectedDistance) { - Vector2 diff = calculatedPath[calculatedPath.Count - 1] - calculatedPath[calculatedPath.Count - 2]; - double d = diff.Length; + // The last length is always incorrect + cumulativeLength.RemoveAt(cumulativeLength.Count - 1); - if (d <= 0) + int pathEndIndex = calculatedPath.Count - 1; + + if (calculatedLength > expectedDistance) + { + // The path will be shortened further, in which case we should trim any more unnecessary lengths and their associated path segments + while (cumulativeLength.Count > 0 && cumulativeLength[cumulativeLength.Count - 1] > expectedDistance) + { + cumulativeLength.RemoveAt(cumulativeLength.Count - 1); + calculatedPath.RemoveAt(pathEndIndex--); + } + } + + if (pathEndIndex <= 0) + { + // The expected distance is negative or zero + // TODO: Perhaps negative path lengths should be disallowed altogether return; + } - calculatedPath[calculatedPath.Count - 1] += diff * (float)((expectedDistance - l) / d); - cumulativeLength[calculatedPath.Count - 1] = expectedDistance.Value; + // The direction of the segment to shorten or lengthen + Vector2 dir = (calculatedPath[pathEndIndex] - calculatedPath[pathEndIndex - 1]).Normalized(); + + calculatedPath[pathEndIndex] = calculatedPath[pathEndIndex - 1] + dir * (float)(expectedDistance - cumulativeLength[cumulativeLength.Count - 1]); + cumulativeLength.Add(expectedDistance); } } From 12f1c9e088c307ac86a1411fd7e80d6feba7fefd Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 6 Dec 2019 15:29:02 +0900 Subject: [PATCH 25/89] Fix test failure --- osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs index 5142b98898..dbea8d28a6 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs @@ -76,8 +76,7 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestEarlyExit() { - AddStep("apply mods", () => Mods.Value = new[] { new OsuModNightcore() }); - AddStep("load dummy beatmap", () => ResetPlayer(false)); + AddStep("load dummy beatmap", () => ResetPlayer(false, () => Mods.Value = new[] { new OsuModNightcore() })); AddUntilStep("wait for current", () => loader.IsCurrentScreen()); AddAssert("mod rate applied", () => Beatmap.Value.Track.Rate != 1); AddStep("exit loader", () => loader.Exit()); From 2654710d917e47b44f80f38d3600203d14355a0c Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 6 Dec 2019 15:37:00 +0900 Subject: [PATCH 26/89] Add tests and fix negative expected distances --- .../Visual/Gameplay/TestSceneSliderPath.cs | 34 +++++++++++++++++++ osu.Game/Rulesets/Objects/SliderPath.cs | 1 + 2 files changed, 35 insertions(+) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSliderPath.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSliderPath.cs index fe2cc188a5..27d39d0f17 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSliderPath.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSliderPath.cs @@ -146,7 +146,41 @@ namespace osu.Game.Tests.Visual.Gameplay break; } }); + } + [Test] + public void TestLengthenLastSegment() + { + AddStep("create path", () => path.ControlPoints.AddRange(createSegment(PathType.Linear, Vector2.Zero, new Vector2(0, 100), new Vector2(100)))); + AddStep("lengthen last segment", () => path.ExpectedDistance.Value = 300); + } + + [Test] + public void TestShortenLastSegment() + { + AddStep("create path", () => path.ControlPoints.AddRange(createSegment(PathType.Linear, Vector2.Zero, new Vector2(0, 100), new Vector2(100)))); + AddStep("shorten last segment", () => path.ExpectedDistance.Value = 150); + } + + [Test] + public void TestShortenFirstSegment() + { + AddStep("create path", () => path.ControlPoints.AddRange(createSegment(PathType.Linear, Vector2.Zero, new Vector2(0, 100), new Vector2(100)))); + AddStep("shorten first segment", () => path.ExpectedDistance.Value = 50); + } + + [Test] + public void TestShortenToZeroLength() + { + AddStep("create path", () => path.ControlPoints.AddRange(createSegment(PathType.Linear, Vector2.Zero, new Vector2(0, 100), new Vector2(100)))); + AddStep("shorten to 0 length", () => path.ExpectedDistance.Value = 0); + } + + [Test] + public void TestShortenToNegativeLength() + { + AddStep("create path", () => path.ControlPoints.AddRange(createSegment(PathType.Linear, Vector2.Zero, new Vector2(0, 100), new Vector2(100)))); + AddStep("shorten to -10 length", () => path.ExpectedDistance.Value = -10); } private List createSegment(PathType type, params Vector2[] controlPoints) diff --git a/osu.Game/Rulesets/Objects/SliderPath.cs b/osu.Game/Rulesets/Objects/SliderPath.cs index 6c24ee6878..095353e3f3 100644 --- a/osu.Game/Rulesets/Objects/SliderPath.cs +++ b/osu.Game/Rulesets/Objects/SliderPath.cs @@ -262,6 +262,7 @@ namespace osu.Game.Rulesets.Objects { // The expected distance is negative or zero // TODO: Perhaps negative path lengths should be disallowed altogether + cumulativeLength.Add(0); return; } From 3358ab9f8abde79becfd4bca2a56361170aef2e1 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 6 Dec 2019 15:53:01 +0900 Subject: [PATCH 27/89] Adjust diffcalc test expected value The difference is caused by the reworked calculateLength() of SliderPath. This comes as a result of the increased accuracy of path lengthenings due to calculating the final position relative to the second-to-last point, rather than relative to the last point. --- osu.Game.Rulesets.Osu.Tests/OsuDifficultyCalculatorTest.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu.Tests/OsuDifficultyCalculatorTest.cs b/osu.Game.Rulesets.Osu.Tests/OsuDifficultyCalculatorTest.cs index 693faee3b7..85a41137d4 100644 --- a/osu.Game.Rulesets.Osu.Tests/OsuDifficultyCalculatorTest.cs +++ b/osu.Game.Rulesets.Osu.Tests/OsuDifficultyCalculatorTest.cs @@ -14,7 +14,7 @@ namespace osu.Game.Rulesets.Osu.Tests { protected override string ResourceAssembly => "osu.Game.Rulesets.Osu"; - [TestCase(6.931145117263422, "diffcalc-test")] + [TestCase(6.9311451172608853d, "diffcalc-test")] [TestCase(1.0736587013228804d, "zero-length-sliders")] public void Test(double expected, string name) => base.Test(expected, name); From b4e1b5fa983fdb826ec8693b3fabdcd66e03e2c1 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 6 Dec 2019 15:53:19 +0900 Subject: [PATCH 28/89] Explose + use the full calculated length of the path --- .../Blueprints/Sliders/SliderPlacementBlueprint.cs | 3 +-- .../Blueprints/Sliders/SliderSelectionBlueprint.cs | 3 +-- osu.Game/Rulesets/Objects/SliderPath.cs | 14 +++++++++++++- 3 files changed, 15 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs index 6f53a9e4b1..bbdc43e16f 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs @@ -133,8 +133,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders private void updateSlider() { - HitObject.Path.ExpectedDistance.Value = null; - HitObject.Path.ExpectedDistance.Value = composer?.GetSnappedDistanceFromDistance(HitObject.StartTime, (float)HitObject.Path.Distance) ?? (float)HitObject.Path.Distance; + HitObject.Path.ExpectedDistance.Value = composer?.GetSnappedDistanceFromDistance(HitObject.StartTime, (float)HitObject.Path.CalculatedDistance) ?? (float)HitObject.Path.CalculatedDistance; bodyPiece.UpdateFrom(HitObject); headCirclePiece.UpdateFrom(HitObject.HeadCircle); diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs index 8388d78c42..7b3ca29e35 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs @@ -114,8 +114,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders private void onNewControlPoints(Vector2[] controlPoints) { - HitObject.Path.ExpectedDistance.Value = null; - HitObject.Path.ExpectedDistance.Value = composer?.GetSnappedDistanceFromDistance(HitObject.StartTime, (float)HitObject.Path.Distance) ?? (float)HitObject.Path.Distance; + HitObject.Path.ExpectedDistance.Value = composer?.GetSnappedDistanceFromDistance(HitObject.StartTime, (float)HitObject.Path.CalculatedDistance) ?? (float)HitObject.Path.CalculatedDistance; UpdateHitObject(); } diff --git a/osu.Game/Rulesets/Objects/SliderPath.cs b/osu.Game/Rulesets/Objects/SliderPath.cs index 095353e3f3..f6e7c40e12 100644 --- a/osu.Game/Rulesets/Objects/SliderPath.cs +++ b/osu.Game/Rulesets/Objects/SliderPath.cs @@ -103,6 +103,18 @@ namespace osu.Game.Rulesets.Objects } } + /// + /// The distance of the path prior to lengthening/shortening to account for . + /// + public double CalculatedDistance + { + get + { + ensureValid(); + return calculatedLength; + } + } + /// /// Computes the slider path until a given progress that ranges from 0 (beginning of the slider) /// to 1 (end of the slider) and stores the generated path in the given list. @@ -251,7 +263,7 @@ namespace osu.Game.Rulesets.Objects if (calculatedLength > expectedDistance) { // The path will be shortened further, in which case we should trim any more unnecessary lengths and their associated path segments - while (cumulativeLength.Count > 0 && cumulativeLength[cumulativeLength.Count - 1] > expectedDistance) + while (cumulativeLength.Count > 0 && cumulativeLength[cumulativeLength.Count - 1] >= expectedDistance) { cumulativeLength.RemoveAt(cumulativeLength.Count - 1); calculatedPath.RemoveAt(pathEndIndex--); From d29ccdc25e4ea515c54a3de945fd9724271bf3e7 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 6 Dec 2019 16:36:08 +0900 Subject: [PATCH 29/89] Fix selection blueprint not re-snapping the path --- .../Blueprints/Sliders/SliderSelectionBlueprint.cs | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs index 7b3ca29e35..9a504445f4 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs @@ -4,6 +4,7 @@ using System; using System.Diagnostics; using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Primitives; using osu.Framework.Graphics.UserInterface; @@ -44,6 +45,16 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders }; } + private IBindable pathVersion; + + protected override void LoadComplete() + { + base.LoadComplete(); + + pathVersion = HitObject.Path.Version.GetBoundCopy(); + pathVersion.BindValueChanged(_ => updatePath()); + } + protected override void Update() { base.Update(); @@ -112,10 +123,9 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders return insertionIndex; } - private void onNewControlPoints(Vector2[] controlPoints) + private void updatePath() { HitObject.Path.ExpectedDistance.Value = composer?.GetSnappedDistanceFromDistance(HitObject.StartTime, (float)HitObject.Path.CalculatedDistance) ?? (float)HitObject.Path.CalculatedDistance; - UpdateHitObject(); } From 2b5f9515de1b8fe386a76e6f2c1398912005dd5c Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 6 Dec 2019 17:03:54 +0900 Subject: [PATCH 30/89] Fix multiple control point deletions --- .../Components/PathControlPointVisualiser.cs | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs index d599ebd893..cbc2a20328 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs @@ -14,6 +14,7 @@ using osu.Framework.Input; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; using osu.Game.Graphics.UserInterface; +using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Screens.Edit.Compose; using osuTK; @@ -100,21 +101,15 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components private bool deleteSelected() { - int countDeleted = 0; - - foreach (var piece in Pieces) - { - if (piece.IsSelected.Value) - { - slider.Path.ControlPoints.RemoveAt(piece.Index); - countDeleted++; - } - } + List toRemove = Pieces.Where(p => p.IsSelected.Value).Select(p => p.Index).Select(i => slider.Path.ControlPoints[i]).ToList(); // Ensure that there are any points to be deleted - if (countDeleted == 0) + if (toRemove.Count == 0) return false; + foreach (var c in toRemove) + slider.Path.ControlPoints.Remove(c); + // If there are 0 remaining control points, treat the slider as being deleted if (slider.Path.ControlPoints.Count == 0) { From 16f8341a0228ebb5fb92c8070918537b980352cb Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 6 Dec 2019 18:49:01 +0900 Subject: [PATCH 31/89] Handle control point positional updates within SliderPath --- .../Components/PathControlPointVisualiser.cs | 8 -------- osu.Game.Rulesets.Osu/Objects/Slider.cs | 2 ++ osu.Game/Rulesets/Objects/SliderPath.cs | 20 +++++++++++++++++++ 3 files changed, 22 insertions(+), 8 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs index cbc2a20328..974b611533 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs @@ -117,14 +117,6 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components return true; } - // Make control points relative - Vector2 first = slider.Path.ControlPoints[0].Position.Value; - for (int i = 0; i < slider.Path.ControlPoints.Count; i++) - slider.Path.ControlPoints[i].Position.Value = slider.Path.ControlPoints[i].Position.Value - first; - - // The slider's position defines the position of the first control point, and all further control points are relative to that point - slider.Position += first; - // Since pieces are re-used, they will not point to the deleted control points while remaining selected foreach (var piece in Pieces) piece.IsSelected.Value = false; diff --git a/osu.Game.Rulesets.Osu/Objects/Slider.cs b/osu.Game.Rulesets.Osu/Objects/Slider.cs index b68595c67e..09657b2d47 100644 --- a/osu.Game.Rulesets.Osu/Objects/Slider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Slider.cs @@ -100,6 +100,8 @@ namespace osu.Game.Rulesets.Osu.Objects { SamplesBindable.ItemsAdded += _ => updateNestedSamples(); SamplesBindable.ItemsRemoved += _ => updateNestedSamples(); + + Path.OffsetChanged += offset => Position += offset; } protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty) diff --git a/osu.Game/Rulesets/Objects/SliderPath.cs b/osu.Game/Rulesets/Objects/SliderPath.cs index f6e7c40e12..969cdcc463 100644 --- a/osu.Game/Rulesets/Objects/SliderPath.cs +++ b/osu.Game/Rulesets/Objects/SliderPath.cs @@ -16,6 +16,12 @@ namespace osu.Game.Rulesets.Objects { public class SliderPath { + /// + /// Invoked when the offset of the path changes. + /// The provided value indicates the offset, and should be used to re-calculate the position of the containing drawable. + /// + public event Action OffsetChanged; + /// /// The current version of this . Updated when any change to the path occurs. /// @@ -62,6 +68,20 @@ namespace osu.Game.Rulesets.Objects foreach (var c in items) c.Changed -= invalidate; + // Make all control points relative to the first one + if (ControlPoints.Count > 0) + { + Vector2 first = ControlPoints[0].Position.Value; + + if (first != Vector2.Zero) + { + foreach (var c in ControlPoints) + c.Position.Value -= first; + + OffsetChanged?.Invoke(first); + } + } + invalidate(); }; } From 52dd7bf716fa277fc157397a2cdcebe0c134e180 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 6 Dec 2019 18:49:14 +0900 Subject: [PATCH 32/89] Fix deleting the first control point not working --- .../Sliders/Components/PathControlPointVisualiser.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs index 974b611533..ee4a37a4f2 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs @@ -108,7 +108,14 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components return false; foreach (var c in toRemove) + { + // The first control point in the slider must have a type, so take it from the previous "first" one + // Todo: Should be handled within SliderPath itself + if (c == slider.Path.ControlPoints[0] && slider.Path.ControlPoints.Count > 1 && slider.Path.ControlPoints[1].Type.Value == null) + slider.Path.ControlPoints[1].Type.Value = slider.Path.ControlPoints[0].Type.Value; + slider.Path.ControlPoints.Remove(c); + } // If there are 0 remaining control points, treat the slider as being deleted if (slider.Path.ControlPoints.Count == 0) From 680b2653aeedbaa02cf359f6079347931db267c6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 6 Dec 2019 18:49:03 +0900 Subject: [PATCH 33/89] Improve animation of popup dialog buttons --- .../UserInterface/TestScenePopupDialog.cs | 22 ++++++++++++++----- .../Graphics/UserInterface/DialogButton.cs | 13 ++++++++--- 2 files changed, 27 insertions(+), 8 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestScenePopupDialog.cs b/osu.Game.Tests/Visual/UserInterface/TestScenePopupDialog.cs index 3d39bb7003..7207506ccd 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestScenePopupDialog.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestScenePopupDialog.cs @@ -1,9 +1,12 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; +using System.Collections.Generic; using NUnit.Framework; using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; +using osu.Game.Graphics.UserInterface; using osu.Game.Overlays.Dialog; namespace osu.Game.Tests.Visual.UserInterface @@ -11,13 +14,22 @@ namespace osu.Game.Tests.Visual.UserInterface [TestFixture] public class TestScenePopupDialog : OsuTestScene { + public override IReadOnlyList RequiredTypes => new[] + { + typeof(PopupDialogOkButton), + typeof(PopupDialogCancelButton), + typeof(PopupDialogButton), + typeof(DialogButton), + }; + public TestScenePopupDialog() { - Add(new TestPopupDialog - { - RelativeSizeAxes = Axes.Both, - State = { Value = Framework.Graphics.Containers.Visibility.Visible }, - }); + AddStep("new popup", () => + Add(new TestPopupDialog + { + RelativeSizeAxes = Axes.Both, + State = { Value = Framework.Graphics.Containers.Visibility.Visible }, + })); } private class TestPopupDialog : PopupDialog diff --git a/osu.Game/Graphics/UserInterface/DialogButton.cs b/osu.Game/Graphics/UserInterface/DialogButton.cs index 927ad13829..15c09b10b4 100644 --- a/osu.Game/Graphics/UserInterface/DialogButton.cs +++ b/osu.Game/Graphics/UserInterface/DialogButton.cs @@ -20,6 +20,7 @@ namespace osu.Game.Graphics.UserInterface { public class DialogButton : OsuClickableContainer { + private const float idle_width = 0.8f; private const float hover_width = 0.9f; private const float hover_duration = 500; private const float glow_fade_duration = 250; @@ -99,7 +100,7 @@ namespace osu.Game.Graphics.UserInterface RelativeSizeAxes = Axes.Both, Origin = Anchor.Centre, Anchor = Anchor.Centre, - Width = 0.8f, + Width = idle_width, Masking = true, MaskingSmoothness = 2, EdgeEffect = new EdgeEffectParameters @@ -199,14 +200,18 @@ namespace osu.Game.Graphics.UserInterface public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => backgroundContainer.ReceivePositionalInputAt(screenSpacePos); + private bool clicked; + protected override bool OnClick(ClickEvent e) { + clicked = true; colourContainer.ResizeTo(new Vector2(1.5f, 1f), click_duration, Easing.In); flash(); this.Delay(click_duration).Schedule(delegate { - colourContainer.ResizeTo(new Vector2(0.8f, 1f)); + clicked = false; + colourContainer.ResizeTo(new Vector2(idle_width, 1f)); spriteText.Spacing = Vector2.Zero; glowContainer.FadeOut(); }); @@ -230,6 +235,8 @@ namespace osu.Game.Graphics.UserInterface private void selectionChanged(ValueChangedEvent args) { + if (clicked) return; + if (args.NewValue) { spriteText.TransformSpacingTo(hoverSpacing, hover_duration, Easing.OutElastic); @@ -238,7 +245,7 @@ namespace osu.Game.Graphics.UserInterface } else { - colourContainer.ResizeTo(new Vector2(0.8f, 1f), hover_duration, Easing.OutElastic); + colourContainer.ResizeTo(new Vector2(idle_width, 1f), hover_duration, Easing.OutElastic); spriteText.TransformSpacingTo(Vector2.Zero, hover_duration, Easing.OutElastic); glowContainer.FadeOut(glow_fade_duration, Easing.Out); } From af2305bb77491397b87aebc7f9f3d1ed49de31bf Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 6 Dec 2019 18:53:30 +0900 Subject: [PATCH 34/89] Add null check --- osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs b/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs index ec9792d59d..fa701a3c41 100644 --- a/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs @@ -50,7 +50,8 @@ namespace osu.Game.Rulesets.Osu.Skinning case OsuSkinComponents.SliderFollowCircle: var followCircle = this.GetAnimation("sliderfollowcircle", true, true); - followCircle.Scale *= 0.5f; + if (followCircle != null) + followCircle.Scale *= 0.5f; return followCircle; case OsuSkinComponents.SliderBall: From f958485be1c268d53967522ccb3481716e21d3a7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 6 Dec 2019 18:54:54 +0900 Subject: [PATCH 35/89] Add comment about size change --- osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs b/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs index fa701a3c41..f5b7d9166f 100644 --- a/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs @@ -51,6 +51,7 @@ namespace osu.Game.Rulesets.Osu.Skinning case OsuSkinComponents.SliderFollowCircle: var followCircle = this.GetAnimation("sliderfollowcircle", true, true); if (followCircle != null) + // follow circles are 2x the hitcircle resolution in legacy skins (since they are scaled down from >1x followCircle.Scale *= 0.5f; return followCircle; From 41437242a29c4fdd7dbae5055dd679fd4763e149 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 6 Dec 2019 19:39:25 +0900 Subject: [PATCH 36/89] Add initial path type progression support --- .../Sliders/SliderPlacementBlueprint.cs | 51 +++++++++++++++++-- 1 file changed, 46 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs index bbdc43e16f..cd93aa0074 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs @@ -30,6 +30,9 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders private InputManager inputManager; private PlacementState state; + private PathControlPoint segmentStart; + private PathControlPoint cursor; + private int currentSegmentLength = 1; [Resolved(CanBeNull = true)] private HitObjectComposer composer { get; set; } @@ -38,7 +41,9 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders : base(new Objects.Slider()) { RelativeSizeAxes = Axes.Both; - HitObject.Path.ControlPoints.Add(new PathControlPoint { Position = { Value = Vector2.Zero } }); + + segmentStart = HitObject.Path.ControlPoints[0]; + } [BackgroundDependencyLoader] @@ -70,9 +75,11 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders break; case PlacementState.Body: + ensureCursor(); + // The given screen-space position may have been externally snapped, but the unsnapped position from the input manager // is used instead since snapping control points doesn't make much sense - HitObject.Path.ControlPoints.Last().Position.Value = ToLocalSpace(inputManager.CurrentState.Mouse.Position) - HitObject.Position; + cursor.Position.Value = ToLocalSpace(inputManager.CurrentState.Mouse.Position) - HitObject.Position; break; } } @@ -89,7 +96,10 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders switch (e.Button) { case MouseButton.Left: - HitObject.Path.ControlPoints.Add(new PathControlPoint { Position = { Value = HitObject.Path.ControlPoints.Last().Position.Value } }); + ensureCursor(); + + // Detatch the cursor + cursor = null; break; } @@ -108,8 +118,11 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders protected override bool OnDoubleClick(DoubleClickEvent e) { - // At the point of a double click, there's guaranteed to be at least two points - one from the click, and one from the cursor - HitObject.Path.ControlPoints[HitObject.Path.ControlPoints.Count - 2].Type.Value = PathType.Bezier; + // Todo: This should all not occur on double click, but rather if the previous control point is hovered. + segmentStart = HitObject.Path.ControlPoints[HitObject.Path.ControlPoints.Count - 1]; + segmentStart.Type.Value = PathType.Linear; + + currentSegmentLength = 1; return true; } @@ -131,6 +144,34 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders updateSlider(); } + private void updatePathType() + { + switch (currentSegmentLength) + { + case 1: + case 2: + segmentStart.Type.Value = PathType.Linear; + break; + case 3: + segmentStart.Type.Value = PathType.PerfectCurve; + break; + default: + segmentStart.Type.Value = PathType.Bezier; + break; + } + } + + private void ensureCursor() + { + if (cursor == null) + { + HitObject.Path.ControlPoints.Add(cursor = new PathControlPoint { Position = { Value = Vector2.Zero } }); + currentSegmentLength++; + + updatePathType(); + } + } + private void updateSlider() { HitObject.Path.ExpectedDistance.Value = composer?.GetSnappedDistanceFromDistance(HitObject.StartTime, (float)HitObject.Path.CalculatedDistance) ?? (float)HitObject.Path.CalculatedDistance; From 5347e7c4a2aadd5694722786f57b45b8f2ab65d2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 6 Dec 2019 20:14:06 +0900 Subject: [PATCH 37/89] Apply code quality improvements --- osu.Game/Configuration/SettingSourceAttribute.cs | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/osu.Game/Configuration/SettingSourceAttribute.cs b/osu.Game/Configuration/SettingSourceAttribute.cs index dceef1cb7d..056fa8bcc0 100644 --- a/osu.Game/Configuration/SettingSourceAttribute.cs +++ b/osu.Game/Configuration/SettingSourceAttribute.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; -using System.Linq; using System.Reflection; using JetBrains.Annotations; using osu.Framework.Bindables; @@ -36,11 +35,13 @@ namespace osu.Game.Configuration { public static IEnumerable CreateSettingsControls(this object obj) { - var configProperties = obj.GetType().GetProperties(BindingFlags.GetProperty | BindingFlags.Public | BindingFlags.Instance).Where(p => p.GetCustomAttribute(true) != null); - - foreach (var property in configProperties) + foreach (var property in obj.GetType().GetProperties(BindingFlags.GetProperty | BindingFlags.Public | BindingFlags.Instance)) { var attr = property.GetCustomAttribute(true); + + if (attr == null) + continue; + var prop = property.GetValue(obj); switch (prop) @@ -91,9 +92,7 @@ namespace osu.Game.Configuration break; case IBindable bindable: - var dropdownType = typeof(SettingsEnumDropdown<>).MakeGenericType(bindable.GetType().GetGenericArguments()[0]); - var dropdown = (Drawable)Activator.CreateInstance(dropdownType); dropdown.GetType().GetProperty(nameof(IHasCurrentValue.Current))?.SetValue(dropdown, obj); From b9d12e5fe4ba3446e168355220bdf9f35ccf36ec Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 6 Dec 2019 20:53:40 +0900 Subject: [PATCH 38/89] Fix nested hitobjects not updating --- .../Objects/JuiceStream.cs | 18 ++++++++++++++- osu.Game.Rulesets.Osu/Objects/Slider.cs | 23 ++++++++++++++++--- 2 files changed, 37 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs b/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs index 33780427b6..366f10d61d 100644 --- a/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs +++ b/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs @@ -116,7 +116,23 @@ namespace osu.Game.Rulesets.Catch.Objects public double Duration => EndTime - StartTime; - public SliderPath Path { get; set; } + private readonly SliderPath path = new SliderPath(new[] { new PathControlPoint { Type = { Value = PathType.Linear } } }); + + public SliderPath Path + { + get => path; + set + { + path.ControlPoints.Clear(); + path.ExpectedDistance.Value = null; + + if (value != null) + { + path.ControlPoints.AddRange(value.ControlPoints); + path.ExpectedDistance.Value = value.ExpectedDistance.Value; + } + } + } public double Distance => Path.Distance; diff --git a/osu.Game.Rulesets.Osu/Objects/Slider.cs b/osu.Game.Rulesets.Osu/Objects/Slider.cs index 09657b2d47..4ba5265d17 100644 --- a/osu.Game.Rulesets.Osu/Objects/Slider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Slider.cs @@ -28,7 +28,23 @@ namespace osu.Game.Rulesets.Osu.Objects public Vector2 StackedPositionAt(double t) => StackedPosition + this.CurvePositionAt(t); - public SliderPath Path { get; set; } = new SliderPath(new[] { new PathControlPoint { Type = { Value = PathType.Bezier } } }); + private readonly SliderPath path = new SliderPath(new[] { new PathControlPoint { Type = { Value = PathType.Linear } } }); + + public SliderPath Path + { + get => path; + set + { + path.ControlPoints.Clear(); + path.ExpectedDistance.Value = null; + + if (value != null) + { + path.ControlPoints.AddRange(value.ControlPoints); + path.ExpectedDistance.Value = value.ExpectedDistance.Value; + } + } + } public double Distance => Path.Distance; @@ -38,8 +54,6 @@ namespace osu.Game.Rulesets.Osu.Objects set { base.Position = value; - endPositionCache.Invalidate(); - updateNestedPositions(); } } @@ -102,6 +116,7 @@ namespace osu.Game.Rulesets.Osu.Objects SamplesBindable.ItemsRemoved += _ => updateNestedSamples(); Path.OffsetChanged += offset => Position += offset; + Path.Version.ValueChanged += _ => updateNestedPositions(); } protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty) @@ -179,6 +194,8 @@ namespace osu.Game.Rulesets.Osu.Objects private void updateNestedPositions() { + endPositionCache.Invalidate(); + if (HeadCircle != null) HeadCircle.Position = Position; From 54798b134e99fb136fc50a5749a8d38a2b201892 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Sun, 8 Dec 2019 03:16:41 +0900 Subject: [PATCH 39/89] Add accessibility modifiers --- osu.Game/Screens/Play/PlayerLoader.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs index 774ca02be1..57021dfc68 100644 --- a/osu.Game/Screens/Play/PlayerLoader.cs +++ b/osu.Game/Screens/Play/PlayerLoader.cs @@ -55,9 +55,9 @@ namespace osu.Game.Screens.Play protected override bool PlayResumeSound => false; - protected Task LoadTask; + protected Task LoadTask { get; private set; } - protected Task DisposalTask; + protected Task DisposalTask { get; private set; } private InputManager inputManager; private IdleTracker idleTracker; From ff8544597c3bd5d0fa1dc26b19f469dcbbdc0632 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 7 Dec 2019 19:51:54 +0100 Subject: [PATCH 40/89] Add explicit app manifest to desktop project After the .NET Core bump to version 3.0 in the 2019.1011.0 release, reports popped up of the game not starting any more on some computers using Intel graphics cards (HD 3000 in particular). After investigation the auto-generated application manifest changed in .NET Core 3.0. In particular this seems to be a root cause for the failed start-ups on Intel cards, due to a Windows version compatibility section appearing. The section in turn affects some WinAPI calls like GetVersionEx, which will return major version 10 instead of 6 if compatibility with Windows 10 is declared. This combined with a broken check in the Intel OpenGL driver caused the crashes. To resolve this without having to patch binaries, add an explicit application manifest to the desktop project with the compatibility section removed. --- osu.Desktop/app.manifest | 20 ++++++++++++++++++++ osu.Desktop/osu.Desktop.csproj | 1 + 2 files changed, 21 insertions(+) create mode 100644 osu.Desktop/app.manifest diff --git a/osu.Desktop/app.manifest b/osu.Desktop/app.manifest new file mode 100644 index 0000000000..2e9127bf44 --- /dev/null +++ b/osu.Desktop/app.manifest @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + true + + + \ No newline at end of file diff --git a/osu.Desktop/osu.Desktop.csproj b/osu.Desktop/osu.Desktop.csproj index 453cf6f94d..01e4ada2f1 100644 --- a/osu.Desktop/osu.Desktop.csproj +++ b/osu.Desktop/osu.Desktop.csproj @@ -8,6 +8,7 @@ osu!lazer osu!lazer lazer.ico + app.manifest 0.0.0 0.0.0 From 929be3e9e7e32954385f36f348f9cea8d9a7595c Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sun, 8 Dec 2019 12:34:07 +0300 Subject: [PATCH 41/89] Highlight own score in BeatmapSetOverlay --- .../Overlays/BeatmapSet/Scores/ScoreTable.cs | 2 +- .../Scores/ScoreTableRowBackground.cs | 24 +++++++++++++------ 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs index 58f5f02956..f6723839b2 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs @@ -63,7 +63,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores return; for (int i = 0; i < value.Count; i++) - backgroundFlow.Add(new ScoreTableRowBackground(i)); + backgroundFlow.Add(new ScoreTableRowBackground(i, value[i])); Columns = createHeaders(value[0]); Content = value.Select((s, i) => createContent(i, s)).ToArray().ToRectangular(); diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTableRowBackground.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTableRowBackground.cs index d820f4d89d..1e10c200ab 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTableRowBackground.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTableRowBackground.cs @@ -7,6 +7,8 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Events; using osu.Game.Graphics; +using osu.Game.Online.API; +using osu.Game.Scoring; namespace osu.Game.Overlays.BeatmapSet.Scores { @@ -17,8 +19,14 @@ namespace osu.Game.Overlays.BeatmapSet.Scores private readonly Box hoveredBackground; private readonly Box background; - public ScoreTableRowBackground(int index) + private readonly int index; + private readonly ScoreInfo score; + + public ScoreTableRowBackground(int index, ScoreInfo score) { + this.index = index; + this.score = score; + RelativeSizeAxes = Axes.X; Height = 25; @@ -37,16 +45,18 @@ namespace osu.Game.Overlays.BeatmapSet.Scores Alpha = 0, }, }; - - if (index % 2 != 0) - background.Alpha = 0; } [BackgroundDependencyLoader] - private void load(OsuColour colours) + private void load(OsuColour colours, IAPIProvider api) { - hoveredBackground.Colour = colours.Gray4; - background.Colour = colours.Gray3; + var isOwnScore = api.LocalUser.Value.Id == score.UserID; + + if (index % 2 != 0 && !isOwnScore) + background.Alpha = 0; + + hoveredBackground.Colour = isOwnScore ? colours.GreenDark : colours.Gray4; + background.Colour = isOwnScore ? colours.GreenDarker : colours.Gray3; } protected override bool OnHover(HoverEvent e) From 13b891f3f4ed6eae1c572737238a005e199efbd1 Mon Sep 17 00:00:00 2001 From: mcendu Date: Sun, 8 Dec 2019 20:05:02 +0800 Subject: [PATCH 42/89] Crude legacy cursor rotation support --- osu.Game.Rulesets.Osu/Skinning/LegacyCursor.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/LegacyCursor.cs b/osu.Game.Rulesets.Osu/Skinning/LegacyCursor.cs index 470ba3acae..6bef90fbaa 100644 --- a/osu.Game.Rulesets.Osu/Skinning/LegacyCursor.cs +++ b/osu.Game.Rulesets.Osu/Skinning/LegacyCursor.cs @@ -19,6 +19,8 @@ namespace osu.Game.Rulesets.Osu.Skinning Origin = Anchor.Centre; } + private NonPlayfieldSprite cursor; + [BackgroundDependencyLoader] private void load(ISkinSource skin) { @@ -30,13 +32,14 @@ namespace osu.Game.Rulesets.Osu.Skinning Anchor = Anchor.Centre, Origin = Anchor.Centre, }, - new NonPlayfieldSprite + cursor = new NonPlayfieldSprite { Texture = skin.GetTexture("cursor"), Anchor = Anchor.Centre, Origin = Anchor.Centre, } }; + cursor.Spin(10000, RotationDirection.Clockwise); } } } From 4cd0dd7856e8b553a9698d86dc114f0ca0768a92 Mon Sep 17 00:00:00 2001 From: mcendu Date: Sun, 8 Dec 2019 20:47:28 +0800 Subject: [PATCH 43/89] Move transformation to LoadComplete --- osu.Game.Rulesets.Osu/Skinning/LegacyCursor.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game.Rulesets.Osu/Skinning/LegacyCursor.cs b/osu.Game.Rulesets.Osu/Skinning/LegacyCursor.cs index 6bef90fbaa..11039227c6 100644 --- a/osu.Game.Rulesets.Osu/Skinning/LegacyCursor.cs +++ b/osu.Game.Rulesets.Osu/Skinning/LegacyCursor.cs @@ -39,6 +39,10 @@ namespace osu.Game.Rulesets.Osu.Skinning Origin = Anchor.Centre, } }; + } + + protected override void LoadComplete() + { cursor.Spin(10000, RotationDirection.Clockwise); } } From 8956768fe0d49a9d433b7ff88b601f4d6dd90e1e Mon Sep 17 00:00:00 2001 From: Joehu Date: Sun, 8 Dec 2019 08:55:45 -0800 Subject: [PATCH 44/89] Fix mod buttons being selected when drag scrolling overlay --- osu.Game/Overlays/Mods/ModButton.cs | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/osu.Game/Overlays/Mods/ModButton.cs b/osu.Game/Overlays/Mods/ModButton.cs index c6b4787ff1..ce623dc182 100644 --- a/osu.Game/Overlays/Mods/ModButton.cs +++ b/osu.Game/Overlays/Mods/ModButton.cs @@ -167,10 +167,6 @@ namespace osu.Game.Overlays.Mods { switch (e.Button) { - case MouseButton.Left: - SelectNext(1); - break; - case MouseButton.Right: SelectNext(-1); break; @@ -180,6 +176,15 @@ namespace osu.Game.Overlays.Mods return true; } + protected override bool OnClick(ClickEvent e) + { + scaleContainer.ScaleTo(1, 500, Easing.OutElastic); + + SelectNext(1); + + return base.OnClick(e); + } + /// /// Select the next available mod in a specified direction. /// From 463b6c00300b1686fe4eb66742ca0f63ea373e03 Mon Sep 17 00:00:00 2001 From: Joehu Date: Sun, 8 Dec 2019 09:04:34 -0800 Subject: [PATCH 45/89] Remove whitespace --- osu.Game/Overlays/Mods/ModButton.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Mods/ModButton.cs b/osu.Game/Overlays/Mods/ModButton.cs index ce623dc182..5af3ebab3b 100644 --- a/osu.Game/Overlays/Mods/ModButton.cs +++ b/osu.Game/Overlays/Mods/ModButton.cs @@ -179,7 +179,7 @@ namespace osu.Game.Overlays.Mods protected override bool OnClick(ClickEvent e) { scaleContainer.ScaleTo(1, 500, Easing.OutElastic); - + SelectNext(1); return base.OnClick(e); From e394b28799f4df5e7e01e87dbed07a8bc50127a2 Mon Sep 17 00:00:00 2001 From: Joehu Date: Sun, 8 Dec 2019 09:12:32 -0800 Subject: [PATCH 46/89] Remove redundant transform --- osu.Game/Overlays/Mods/ModButton.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/osu.Game/Overlays/Mods/ModButton.cs b/osu.Game/Overlays/Mods/ModButton.cs index 5af3ebab3b..69a4a4181a 100644 --- a/osu.Game/Overlays/Mods/ModButton.cs +++ b/osu.Game/Overlays/Mods/ModButton.cs @@ -178,11 +178,9 @@ namespace osu.Game.Overlays.Mods protected override bool OnClick(ClickEvent e) { - scaleContainer.ScaleTo(1, 500, Easing.OutElastic); - SelectNext(1); - return base.OnClick(e); + return true; } /// From c2a40c574dabf83556a23272e869768759b966d5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 9 Dec 2019 02:13:47 +0900 Subject: [PATCH 47/89] Split out if statement for readability --- .../Overlays/BeatmapSet/Scores/ScoreTableRowBackground.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTableRowBackground.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTableRowBackground.cs index 1e10c200ab..724a7f8b55 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTableRowBackground.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTableRowBackground.cs @@ -52,11 +52,14 @@ namespace osu.Game.Overlays.BeatmapSet.Scores { var isOwnScore = api.LocalUser.Value.Id == score.UserID; - if (index % 2 != 0 && !isOwnScore) + if (isOwnScore) + background.Colour = colours.GreenDarker; + else if (index % 2 == 0) + background.Colour = colours.Gray3; + else background.Alpha = 0; hoveredBackground.Colour = isOwnScore ? colours.GreenDark : colours.Gray4; - background.Colour = isOwnScore ? colours.GreenDarker : colours.Gray3; } protected override bool OnHover(HoverEvent e) From 5cf35869e956ffcf0fc2671705bf3cb7c33cab2c Mon Sep 17 00:00:00 2001 From: Joehu Date: Sun, 8 Dec 2019 10:40:39 -0800 Subject: [PATCH 48/89] Fix key config search not clearing after pressing escape --- osu.Game/Overlays/SettingsSubPanel.cs | 18 +----------------- 1 file changed, 1 insertion(+), 17 deletions(-) diff --git a/osu.Game/Overlays/SettingsSubPanel.cs b/osu.Game/Overlays/SettingsSubPanel.cs index 5000156e97..2235f9f338 100644 --- a/osu.Game/Overlays/SettingsSubPanel.cs +++ b/osu.Game/Overlays/SettingsSubPanel.cs @@ -4,12 +4,10 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; -using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; -using osu.Game.Input.Bindings; using osu.Game.Overlays.Settings; using osu.Game.Screens.Ranking; using osuTK; @@ -36,7 +34,7 @@ namespace osu.Game.Overlays protected override bool DimMainContent => false; // dimming is handled by main overlay - private class BackButton : OsuClickableContainer, IKeyBindingHandler + private class BackButton : OsuClickableContainer { private AspectContainer aspect; @@ -85,20 +83,6 @@ namespace osu.Game.Overlays aspect.ScaleTo(1, 1000, Easing.OutElastic); return base.OnMouseUp(e); } - - public bool OnPressed(GlobalAction action) - { - switch (action) - { - case GlobalAction.Back: - Click(); - return true; - } - - return false; - } - - public bool OnReleased(GlobalAction action) => false; } } } From 9974fff5cc3d52d36e0ebaf2f57cc3af5eddc4ea Mon Sep 17 00:00:00 2001 From: Joehu Date: Sun, 8 Dec 2019 10:51:25 -0800 Subject: [PATCH 49/89] Make sub panel back button inherit osu button --- osu.Game/Overlays/SettingsSubPanel.cs | 32 +++++++++------------------ 1 file changed, 10 insertions(+), 22 deletions(-) diff --git a/osu.Game/Overlays/SettingsSubPanel.cs b/osu.Game/Overlays/SettingsSubPanel.cs index 2235f9f338..1fa233d9d4 100644 --- a/osu.Game/Overlays/SettingsSubPanel.cs +++ b/osu.Game/Overlays/SettingsSubPanel.cs @@ -3,14 +3,14 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; -using osu.Framework.Input.Events; using osu.Game.Graphics; -using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterface; using osu.Game.Overlays.Settings; -using osu.Game.Screens.Ranking; using osuTK; +using osuTK.Graphics; namespace osu.Game.Overlays { @@ -34,21 +34,21 @@ namespace osu.Game.Overlays protected override bool DimMainContent => false; // dimming is handled by main overlay - private class BackButton : OsuClickableContainer + private class BackButton : OsuButton { - private AspectContainer aspect; - [BackgroundDependencyLoader] private void load() { Size = new Vector2(Sidebar.DEFAULT_WIDTH); - Children = new Drawable[] + + BackgroundColour = Color4.Black; + + AddRange(new Drawable[] { - aspect = new AspectContainer + new Container { Anchor = Anchor.Centre, Origin = Anchor.Centre, - RelativeSizeAxes = Axes.Y, Children = new Drawable[] { new SpriteIcon @@ -69,19 +69,7 @@ namespace osu.Game.Overlays }, } } - }; - } - - protected override bool OnMouseDown(MouseDownEvent e) - { - aspect.ScaleTo(0.75f, 2000, Easing.OutQuint); - return base.OnMouseDown(e); - } - - protected override bool OnMouseUp(MouseUpEvent e) - { - aspect.ScaleTo(1, 1000, Easing.OutElastic); - return base.OnMouseUp(e); + }); } } } From b2b252a1cc446f96c306129fe6d6011b07dafe66 Mon Sep 17 00:00:00 2001 From: mcendu Date: Mon, 9 Dec 2019 08:36:07 +0800 Subject: [PATCH 50/89] Allow skin to disable spin --- osu.Game.Rulesets.Osu/Skinning/LegacyCursor.cs | 8 ++++++-- .../Skinning/OsuLegacySkinTransformer.cs | 4 +++- osu.Game.Rulesets.Osu/Skinning/OsuSkinConfiguration.cs | 1 + 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/LegacyCursor.cs b/osu.Game.Rulesets.Osu/Skinning/LegacyCursor.cs index 11039227c6..68cf99caf1 100644 --- a/osu.Game.Rulesets.Osu/Skinning/LegacyCursor.cs +++ b/osu.Game.Rulesets.Osu/Skinning/LegacyCursor.cs @@ -11,15 +11,18 @@ namespace osu.Game.Rulesets.Osu.Skinning { public class LegacyCursor : CompositeDrawable { - public LegacyCursor() + public LegacyCursor(bool spin = true) { Size = new Vector2(50); Anchor = Anchor.Centre; Origin = Anchor.Centre; + + rotate = spin; } private NonPlayfieldSprite cursor; + private bool rotate; [BackgroundDependencyLoader] private void load(ISkinSource skin) @@ -43,7 +46,8 @@ namespace osu.Game.Rulesets.Osu.Skinning protected override void LoadComplete() { - cursor.Spin(10000, RotationDirection.Clockwise); + if (rotate) + cursor.Spin(10000, RotationDirection.Clockwise); } } } diff --git a/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs b/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs index f5b7d9166f..f58b96844e 100644 --- a/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs @@ -81,7 +81,9 @@ namespace osu.Game.Rulesets.Osu.Skinning case OsuSkinComponents.Cursor: if (source.GetTexture("cursor") != null) - return new LegacyCursor(); + { + return new LegacyCursor(GetConfig(OsuSkinConfiguration.CursorRotate)?.Value ?? true); + } return null; diff --git a/osu.Game.Rulesets.Osu/Skinning/OsuSkinConfiguration.cs b/osu.Game.Rulesets.Osu/Skinning/OsuSkinConfiguration.cs index 98219cafe8..5d99960f10 100644 --- a/osu.Game.Rulesets.Osu/Skinning/OsuSkinConfiguration.cs +++ b/osu.Game.Rulesets.Osu/Skinning/OsuSkinConfiguration.cs @@ -11,5 +11,6 @@ namespace osu.Game.Rulesets.Osu.Skinning SliderPathRadius, AllowSliderBallTint, CursorExpand, + CursorRotate } } From 1cf81c49066dbf3fdafc3d029c187ad012b26d0d Mon Sep 17 00:00:00 2001 From: mcendu Date: Mon, 9 Dec 2019 08:37:32 +0800 Subject: [PATCH 51/89] rm unnecessary curlies --- osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs b/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs index f58b96844e..56c8e74509 100644 --- a/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs @@ -81,9 +81,7 @@ namespace osu.Game.Rulesets.Osu.Skinning case OsuSkinComponents.Cursor: if (source.GetTexture("cursor") != null) - { return new LegacyCursor(GetConfig(OsuSkinConfiguration.CursorRotate)?.Value ?? true); - } return null; From eb065286ae5b17081910d708fc1deb2421c969bd Mon Sep 17 00:00:00 2001 From: mcendu Date: Mon, 9 Dec 2019 08:49:44 +0800 Subject: [PATCH 52/89] fix ci --- osu.Game.Rulesets.Osu/Skinning/LegacyCursor.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/LegacyCursor.cs b/osu.Game.Rulesets.Osu/Skinning/LegacyCursor.cs index 68cf99caf1..85ae9d0fc4 100644 --- a/osu.Game.Rulesets.Osu/Skinning/LegacyCursor.cs +++ b/osu.Game.Rulesets.Osu/Skinning/LegacyCursor.cs @@ -22,7 +22,7 @@ namespace osu.Game.Rulesets.Osu.Skinning } private NonPlayfieldSprite cursor; - private bool rotate; + private readonly bool rotate; [BackgroundDependencyLoader] private void load(ISkinSource skin) From 76aabdd2971effca216074ee33e7399b8e8b92b0 Mon Sep 17 00:00:00 2001 From: mcendu Date: Mon, 9 Dec 2019 12:11:04 +0800 Subject: [PATCH 53/89] rename field rotate to spin --- osu.Game.Rulesets.Osu/Skinning/LegacyCursor.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/LegacyCursor.cs b/osu.Game.Rulesets.Osu/Skinning/LegacyCursor.cs index 85ae9d0fc4..d0d11b9157 100644 --- a/osu.Game.Rulesets.Osu/Skinning/LegacyCursor.cs +++ b/osu.Game.Rulesets.Osu/Skinning/LegacyCursor.cs @@ -18,11 +18,11 @@ namespace osu.Game.Rulesets.Osu.Skinning Anchor = Anchor.Centre; Origin = Anchor.Centre; - rotate = spin; + this.spin = spin; } private NonPlayfieldSprite cursor; - private readonly bool rotate; + private readonly bool spin; [BackgroundDependencyLoader] private void load(ISkinSource skin) @@ -46,7 +46,7 @@ namespace osu.Game.Rulesets.Osu.Skinning protected override void LoadComplete() { - if (rotate) + if (spin) cursor.Spin(10000, RotationDirection.Clockwise); } } From 4905709ea497799416c71fbcdba485612c60b67e Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 9 Dec 2019 13:19:21 +0900 Subject: [PATCH 54/89] Remove unused usings --- .../Blueprints/Sliders/Components/PathControlPointVisualiser.cs | 2 -- .../Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs | 2 -- .../Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs | 2 -- osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs | 1 - osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs | 1 - osu.Game.Rulesets.Osu/Objects/Slider.cs | 1 - .../Rulesets/Objects/Legacy/Catch/ConvertHitObjectParser.cs | 1 - .../Rulesets/Objects/Legacy/Mania/ConvertHitObjectParser.cs | 1 - osu.Game/Rulesets/Objects/Legacy/Osu/ConvertHitObjectParser.cs | 1 - .../Rulesets/Objects/Legacy/Taiko/ConvertHitObjectParser.cs | 1 - 10 files changed, 13 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs index ee4a37a4f2..a9616026f5 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs @@ -1,7 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; using System.Collections.Generic; using System.Linq; using Humanizer; @@ -17,7 +16,6 @@ using osu.Game.Graphics.UserInterface; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Screens.Edit.Compose; -using osuTK; using osuTK.Input; namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs index cd93aa0074..639e18681b 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs @@ -2,9 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; -using System.Linq; using osu.Framework.Allocation; -using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; using osu.Framework.Input; using osu.Framework.Input.Events; diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs index 9a504445f4..7431972673 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs @@ -1,7 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; using System.Diagnostics; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -12,7 +11,6 @@ using osu.Framework.Input.Events; using osu.Game.Graphics.UserInterface; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Objects; -using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects.Drawables; diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs index 166defbc41..c5609b01e0 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs @@ -4,7 +4,6 @@ using System; using osu.Framework.Allocation; using osu.Framework.Bindables; -using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Types; using osuTK; diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs index 8e2f6ffa66..21a3a0d236 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs @@ -3,7 +3,6 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; -using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Scoring; using osuTK; diff --git a/osu.Game.Rulesets.Osu/Objects/Slider.cs b/osu.Game.Rulesets.Osu/Objects/Slider.cs index 4ba5265d17..4c299fd7c2 100644 --- a/osu.Game.Rulesets.Osu/Objects/Slider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Slider.cs @@ -6,7 +6,6 @@ using osu.Game.Rulesets.Objects.Types; using System.Collections.Generic; using osu.Game.Rulesets.Objects; using System.Linq; -using osu.Framework.Bindables; using osu.Framework.Caching; using osu.Game.Audio; using osu.Game.Beatmaps; diff --git a/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertHitObjectParser.cs index afa1f2996e..43e8d01297 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertHitObjectParser.cs @@ -3,7 +3,6 @@ using osuTK; using osu.Game.Audio; -using osu.Game.Rulesets.Objects.Types; using System.Collections.Generic; namespace osu.Game.Rulesets.Objects.Legacy.Catch diff --git a/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHitObjectParser.cs index d1a8adc2b7..f94c4aaa75 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHitObjectParser.cs @@ -3,7 +3,6 @@ using osuTK; using osu.Game.Audio; -using osu.Game.Rulesets.Objects.Types; using System.Collections.Generic; namespace osu.Game.Rulesets.Objects.Legacy.Mania diff --git a/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertHitObjectParser.cs index 6628f7d059..b95ec703b6 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertHitObjectParser.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using osuTK; -using osu.Game.Rulesets.Objects.Types; using System.Collections.Generic; using osu.Game.Audio; diff --git a/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertHitObjectParser.cs index 7b1d64b19f..db65a61c90 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertHitObjectParser.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using osuTK; -using osu.Game.Rulesets.Objects.Types; using System.Collections.Generic; using osu.Game.Audio; From 69deb0ca96f2ef4d22233fd8333311c290b965b0 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Mon, 9 Dec 2019 07:19:55 +0300 Subject: [PATCH 55/89] Fix unavailable replays can be accessed via leaderboard --- osu.Game/Online/API/Requests/Responses/APILegacyScoreInfo.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Online/API/Requests/Responses/APILegacyScoreInfo.cs b/osu.Game/Online/API/Requests/Responses/APILegacyScoreInfo.cs index 79ce04ed66..b941cd8973 100644 --- a/osu.Game/Online/API/Requests/Responses/APILegacyScoreInfo.cs +++ b/osu.Game/Online/API/Requests/Responses/APILegacyScoreInfo.cs @@ -34,7 +34,7 @@ namespace osu.Game.Online.API.Requests.Responses PP = PP, Beatmap = Beatmap, RulesetID = OnlineRulesetID, - Hash = "online", // todo: temporary? + Hash = Replay ? "online" : string.Empty, // todo: temporary? Rank = Rank, Ruleset = ruleset, Mods = mods, From 03d18186c23ab92eddda00a04f958d592ca9abf5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 9 Dec 2019 14:11:44 +0900 Subject: [PATCH 56/89] Fix broken merge --- .../Graphics/UserInterface/DialogButton.cs | 73 ++++++++++--------- 1 file changed, 39 insertions(+), 34 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/DialogButton.cs b/osu.Game/Graphics/UserInterface/DialogButton.cs index 15c09b10b4..aed07e56ee 100644 --- a/osu.Game/Graphics/UserInterface/DialogButton.cs +++ b/osu.Game/Graphics/UserInterface/DialogButton.cs @@ -22,8 +22,8 @@ namespace osu.Game.Graphics.UserInterface { private const float idle_width = 0.8f; private const float hover_width = 0.9f; + private const float hover_duration = 500; - private const float glow_fade_duration = 250; private const float click_duration = 200; public readonly BindableBool Selected = new BindableBool(); @@ -200,30 +200,50 @@ namespace osu.Game.Graphics.UserInterface public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => backgroundContainer.ReceivePositionalInputAt(screenSpacePos); - private bool clicked; + private bool clickAnimating; protected override bool OnClick(ClickEvent e) { - clicked = true; - colourContainer.ResizeTo(new Vector2(1.5f, 1f), click_duration, Easing.In); - flash(); - - this.Delay(click_duration).Schedule(delegate + var flash = new Box { - clicked = false; - colourContainer.ResizeTo(new Vector2(idle_width, 1f)); - spriteText.Spacing = Vector2.Zero; - glowContainer.FadeOut(); - }); + RelativeSizeAxes = Axes.Both, + Colour = ButtonColour, + Blending = BlendingParameters.Additive, + Alpha = 0.05f + }; + + colourContainer.Add(flash); + flash.FadeOutFromOne(100).Expire(); + + clickAnimating = true; + colourContainer.ResizeWidthTo(colourContainer.Width * 1.05f, 100, Easing.OutQuint) + .OnComplete(_ => + { + clickAnimating = false; + Selected.TriggerChange(); + }); return base.OnClick(e); } + protected override bool OnMouseDown(MouseDownEvent e) + { + colourContainer.ResizeWidthTo(hover_width * 0.98f, click_duration * 4, Easing.OutQuad); + return base.OnMouseDown(e); + } + + protected override bool OnMouseUp(MouseUpEvent e) + { + if (Selected.Value) + colourContainer.ResizeWidthTo(hover_width, click_duration, Easing.In); + return base.OnMouseUp(e); + } + protected override bool OnHover(HoverEvent e) { base.OnHover(e); - Selected.Value = true; + return true; } @@ -235,38 +255,23 @@ namespace osu.Game.Graphics.UserInterface private void selectionChanged(ValueChangedEvent args) { - if (clicked) return; + if (clickAnimating) + return; if (args.NewValue) { spriteText.TransformSpacingTo(hoverSpacing, hover_duration, Easing.OutElastic); - colourContainer.ResizeTo(new Vector2(hover_width, 1f), hover_duration, Easing.OutElastic); - glowContainer.FadeIn(glow_fade_duration, Easing.Out); + colourContainer.ResizeWidthTo(hover_width, hover_duration, Easing.OutElastic); + glowContainer.FadeIn(hover_duration, Easing.OutQuint); } else { - colourContainer.ResizeTo(new Vector2(idle_width, 1f), hover_duration, Easing.OutElastic); + colourContainer.ResizeWidthTo(idle_width, hover_duration, Easing.OutElastic); spriteText.TransformSpacingTo(Vector2.Zero, hover_duration, Easing.OutElastic); - glowContainer.FadeOut(glow_fade_duration, Easing.Out); + glowContainer.FadeOut(hover_duration, Easing.OutQuint); } } - private void flash() - { - var flash = new Box - { - RelativeSizeAxes = Axes.Both - }; - - colourContainer.Add(flash); - - flash.Colour = ButtonColour; - flash.Blending = BlendingParameters.Additive; - flash.Alpha = 0.3f; - flash.FadeOutFromOne(click_duration); - flash.Expire(); - } - private void updateGlow() { leftGlow.Colour = ColourInfo.GradientHorizontal(new Color4(ButtonColour.R, ButtonColour.G, ButtonColour.B, 0f), ButtonColour); From aff1b93a0790672311926a9e0f6bec711c1ce011 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 9 Dec 2019 14:43:23 +0900 Subject: [PATCH 57/89] Move config retrieval into LegacySliderBall --- osu.Game.Rulesets.Osu/Skinning/LegacyCursor.cs | 12 ++++++------ .../Skinning/OsuLegacySkinTransformer.cs | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/LegacyCursor.cs b/osu.Game.Rulesets.Osu/Skinning/LegacyCursor.cs index d0d11b9157..05b38ae195 100644 --- a/osu.Game.Rulesets.Osu/Skinning/LegacyCursor.cs +++ b/osu.Game.Rulesets.Osu/Skinning/LegacyCursor.cs @@ -11,22 +11,22 @@ namespace osu.Game.Rulesets.Osu.Skinning { public class LegacyCursor : CompositeDrawable { - public LegacyCursor(bool spin = true) + private NonPlayfieldSprite cursor; + private bool spin; + + public LegacyCursor() { Size = new Vector2(50); Anchor = Anchor.Centre; Origin = Anchor.Centre; - - this.spin = spin; } - private NonPlayfieldSprite cursor; - private readonly bool spin; - [BackgroundDependencyLoader] private void load(ISkinSource skin) { + spin = skin.GetConfig(OsuSkinConfiguration.CursorRotate)?.Value ?? true; + InternalChildren = new Drawable[] { new NonPlayfieldSprite diff --git a/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs b/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs index 56c8e74509..f5b7d9166f 100644 --- a/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs @@ -81,7 +81,7 @@ namespace osu.Game.Rulesets.Osu.Skinning case OsuSkinComponents.Cursor: if (source.GetTexture("cursor") != null) - return new LegacyCursor(GetConfig(OsuSkinConfiguration.CursorRotate)?.Value ?? true); + return new LegacyCursor(); return null; From 12cfb7dedb3a8e631abdeb73c18d2169d7dd8255 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Mon, 9 Dec 2019 07:28:11 +0000 Subject: [PATCH 58/89] Bump System.ComponentModel.Annotations from 4.6.0 to 4.7.0 Bumps [System.ComponentModel.Annotations](https://github.com/dotnet/corefx) from 4.6.0 to 4.7.0. - [Release notes](https://github.com/dotnet/corefx/releases) - [Commits](https://github.com/dotnet/corefx/commits) Signed-off-by: dependabot-preview[bot] --- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 086359ee41..ef16738908 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -27,6 +27,6 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index 0eb8d98a63..5090190f28 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -86,7 +86,7 @@ - + From 53f7c753fb83cec669a56a12b9a4a19e096e9390 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 9 Dec 2019 16:44:19 +0900 Subject: [PATCH 59/89] General cleanups --- .../Sliders/Components/PathControlPointVisualiser.cs | 2 +- .../Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs | 3 ++- osu.Game.Tests/Visual/Gameplay/TestSceneSliderPath.cs | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs index a9616026f5..f0888b34fe 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs @@ -99,7 +99,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components private bool deleteSelected() { - List toRemove = Pieces.Where(p => p.IsSelected.Value).Select(p => p.Index).Select(i => slider.Path.ControlPoints[i]).ToList(); + List toRemove = Pieces.Where(p => p.IsSelected.Value).Select(p => slider.Path.ControlPoints[p.Index]).ToList(); // Ensure that there are any points to be deleted if (toRemove.Count == 0) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs index 639e18681b..7dd2017e48 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs @@ -41,7 +41,6 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders RelativeSizeAxes = Axes.Both; segmentStart = HitObject.Path.ControlPoints[0]; - } [BackgroundDependencyLoader] @@ -150,9 +149,11 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders case 2: segmentStart.Type.Value = PathType.Linear; break; + case 3: segmentStart.Type.Value = PathType.PerfectCurve; break; + default: segmentStart.Type.Value = PathType.Bezier; break; diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSliderPath.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSliderPath.cs index 27d39d0f17..606395c289 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSliderPath.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSliderPath.cs @@ -6,7 +6,6 @@ using System.Linq; using NUnit.Framework; using osu.Framework.Graphics; using osu.Framework.Graphics.Lines; -using osu.Framework.MathUtils; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Types; using osuTK; @@ -141,6 +140,7 @@ namespace osu.Game.Tests.Visual.Gameplay case 2: path.ControlPoints.AddRange(createSegment(PathType.PerfectCurve, Vector2.Zero, new Vector2(0, 100))); break; + case 4: path.ControlPoints.AddRange(createSegment(PathType.PerfectCurve, Vector2.Zero, new Vector2(0, 100), new Vector2(100), new Vector2(100, 0))); break; From b764a74919e5da3ac88b576e7b954b4a7bab6463 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Mon, 9 Dec 2019 07:52:22 +0000 Subject: [PATCH 60/89] Bump Microsoft.Win32.Registry from 4.6.0 to 4.7.0 Bumps [Microsoft.Win32.Registry](https://github.com/dotnet/corefx) from 4.6.0 to 4.7.0. - [Release notes](https://github.com/dotnet/corefx/releases) - [Commits](https://github.com/dotnet/corefx/commits) Signed-off-by: dependabot-preview[bot] --- osu.Desktop/osu.Desktop.csproj | 2 +- osu.Game.Tournament/osu.Game.Tournament.csproj | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Desktop/osu.Desktop.csproj b/osu.Desktop/osu.Desktop.csproj index 01e4ada2f1..63aa999a97 100644 --- a/osu.Desktop/osu.Desktop.csproj +++ b/osu.Desktop/osu.Desktop.csproj @@ -28,7 +28,7 @@ - + diff --git a/osu.Game.Tournament/osu.Game.Tournament.csproj b/osu.Game.Tournament/osu.Game.Tournament.csproj index 8e881fdd9c..9cce40c9d3 100644 --- a/osu.Game.Tournament/osu.Game.Tournament.csproj +++ b/osu.Game.Tournament/osu.Game.Tournament.csproj @@ -9,6 +9,6 @@ - + \ No newline at end of file From 3861abce826e0f533f97cc678b5333046409b55f Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Mon, 9 Dec 2019 07:52:23 +0000 Subject: [PATCH 61/89] Bump System.IO.Packaging from 4.6.0 to 4.7.0 Bumps [System.IO.Packaging](https://github.com/dotnet/corefx) from 4.6.0 to 4.7.0. - [Release notes](https://github.com/dotnet/corefx/releases) - [Commits](https://github.com/dotnet/corefx/commits) Signed-off-by: dependabot-preview[bot] --- osu.Desktop/osu.Desktop.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Desktop/osu.Desktop.csproj b/osu.Desktop/osu.Desktop.csproj index 01e4ada2f1..70b9d5c184 100644 --- a/osu.Desktop/osu.Desktop.csproj +++ b/osu.Desktop/osu.Desktop.csproj @@ -24,7 +24,7 @@ - + From eb074b7058cc6586f447d308131db79b8af5724e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 9 Dec 2019 17:34:04 +0900 Subject: [PATCH 62/89] Allow mods to apply to track, not clock --- .../Visual/Gameplay/TestScenePlayerLoader.cs | 4 +- osu.Game/Overlays/MusicController.cs | 4 +- .../Difficulty/DifficultyCalculator.cs | 8 ++-- .../Difficulty/PerformanceCalculator.cs | 8 ++-- ...icableToClock.cs => IApplicableToTrack.cs} | 6 +-- osu.Game/Rulesets/Mods/ModDaycore.cs | 10 ++--- osu.Game/Rulesets/Mods/ModDoubleTime.cs | 2 +- osu.Game/Rulesets/Mods/ModHalfTime.cs | 2 +- osu.Game/Rulesets/Mods/ModNightcore.cs | 10 ++--- osu.Game/Rulesets/Mods/ModTimeAdjust.cs | 12 ++---- osu.Game/Rulesets/Mods/ModTimeRamp.cs | 32 ++++------------ .../Screens/Play/GameplayClockContainer.cs | 37 +++++++++---------- 12 files changed, 52 insertions(+), 83 deletions(-) rename osu.Game/Rulesets/Mods/{IApplicableToClock.cs => IApplicableToTrack.cs} (69%) diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs index dbea8d28a6..f02361e685 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs @@ -57,8 +57,8 @@ namespace osu.Game.Tests.Visual.Gameplay beforeLoadAction?.Invoke(); Beatmap.Value = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo); - foreach (var mod in Mods.Value.OfType()) - mod.ApplyToClock(Beatmap.Value.Track); + foreach (var mod in Mods.Value.OfType()) + mod.ApplyToTrack(Beatmap.Value.Track); InputManager.Child = container = new TestPlayerLoaderContainer( loader = new TestPlayerLoader(() => diff --git a/osu.Game/Overlays/MusicController.cs b/osu.Game/Overlays/MusicController.cs index 5e0a67c2f7..bafdad3508 100644 --- a/osu.Game/Overlays/MusicController.cs +++ b/osu.Game/Overlays/MusicController.cs @@ -261,8 +261,8 @@ namespace osu.Game.Overlays if (allowRateAdjustments) { - foreach (var mod in mods.Value.OfType()) - mod.ApplyToClock(track); + foreach (var mod in mods.Value.OfType()) + mod.ApplyToTrack(track); } } diff --git a/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs b/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs index e31c963403..1902de5bda 100644 --- a/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs +++ b/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs @@ -4,8 +4,8 @@ using System; using System.Collections.Generic; using System.Linq; +using osu.Framework.Audio.Track; using osu.Framework.Extensions.IEnumerableExtensions; -using osu.Framework.Timing; using osu.Game.Beatmaps; using osu.Game.Rulesets.Difficulty.Preprocessing; using osu.Game.Rulesets.Difficulty.Skills; @@ -41,10 +41,10 @@ namespace osu.Game.Rulesets.Difficulty IBeatmap playableBeatmap = beatmap.GetPlayableBeatmap(ruleset.RulesetInfo, mods); - var clock = new StopwatchClock(); - mods.OfType().ForEach(m => m.ApplyToClock(clock)); + var track = new TrackVirtual(10000); + mods.OfType().ForEach(m => m.ApplyToTrack(track)); - return calculate(playableBeatmap, mods, clock.Rate); + return calculate(playableBeatmap, mods, track.Rate); } /// diff --git a/osu.Game/Rulesets/Difficulty/PerformanceCalculator.cs b/osu.Game/Rulesets/Difficulty/PerformanceCalculator.cs index 9ab81b9580..ac3b817840 100644 --- a/osu.Game/Rulesets/Difficulty/PerformanceCalculator.cs +++ b/osu.Game/Rulesets/Difficulty/PerformanceCalculator.cs @@ -3,8 +3,8 @@ using System.Collections.Generic; using System.Linq; +using osu.Framework.Audio.Track; using osu.Framework.Extensions.IEnumerableExtensions; -using osu.Framework.Timing; using osu.Game.Beatmaps; using osu.Game.Rulesets.Mods; using osu.Game.Scoring; @@ -35,9 +35,9 @@ namespace osu.Game.Rulesets.Difficulty protected virtual void ApplyMods(Mod[] mods) { - var clock = new StopwatchClock(); - mods.OfType().ForEach(m => m.ApplyToClock(clock)); - TimeRate = clock.Rate; + var track = new TrackVirtual(10000); + mods.OfType().ForEach(m => m.ApplyToTrack(track)); + TimeRate = track.Rate; } public abstract double Calculate(Dictionary categoryDifficulty = null); diff --git a/osu.Game/Rulesets/Mods/IApplicableToClock.cs b/osu.Game/Rulesets/Mods/IApplicableToTrack.cs similarity index 69% rename from osu.Game/Rulesets/Mods/IApplicableToClock.cs rename to osu.Game/Rulesets/Mods/IApplicableToTrack.cs index e5767b5fbf..4d6d958e82 100644 --- a/osu.Game/Rulesets/Mods/IApplicableToClock.cs +++ b/osu.Game/Rulesets/Mods/IApplicableToTrack.cs @@ -1,15 +1,15 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using osu.Framework.Timing; +using osu.Framework.Audio.Track; namespace osu.Game.Rulesets.Mods { /// /// An interface for mods that make adjustments to the track. /// - public interface IApplicableToClock : IApplicableMod + public interface IApplicableToTrack : IApplicableMod { - void ApplyToClock(IAdjustableClock clock); + void ApplyToTrack(Track track); } } diff --git a/osu.Game/Rulesets/Mods/ModDaycore.cs b/osu.Game/Rulesets/Mods/ModDaycore.cs index 7e6d959119..dcb3cb5597 100644 --- a/osu.Game/Rulesets/Mods/ModDaycore.cs +++ b/osu.Game/Rulesets/Mods/ModDaycore.cs @@ -1,9 +1,8 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using osu.Framework.Audio; +using osu.Framework.Audio.Track; using osu.Framework.Graphics.Sprites; -using osu.Framework.Timing; namespace osu.Game.Rulesets.Mods { @@ -14,12 +13,9 @@ namespace osu.Game.Rulesets.Mods public override IconUsage Icon => FontAwesome.Solid.Question; public override string Description => "Whoaaaaa..."; - public override void ApplyToClock(IAdjustableClock clock) + public override void ApplyToTrack(Track track) { - if (clock is IHasPitchAdjust pitchAdjust) - pitchAdjust.PitchAdjust *= RateAdjust; - else - base.ApplyToClock(clock); + track.Frequency.Value *= RateAdjust; } } } diff --git a/osu.Game/Rulesets/Mods/ModDoubleTime.cs b/osu.Game/Rulesets/Mods/ModDoubleTime.cs index a5e76e32b1..5e685b040e 100644 --- a/osu.Game/Rulesets/Mods/ModDoubleTime.cs +++ b/osu.Game/Rulesets/Mods/ModDoubleTime.cs @@ -8,7 +8,7 @@ using osu.Game.Graphics; namespace osu.Game.Rulesets.Mods { - public abstract class ModDoubleTime : ModTimeAdjust, IApplicableToClock + public abstract class ModDoubleTime : ModTimeAdjust { public override string Name => "Double Time"; public override string Acronym => "DT"; diff --git a/osu.Game/Rulesets/Mods/ModHalfTime.cs b/osu.Game/Rulesets/Mods/ModHalfTime.cs index 27369f4c30..d17ddd2253 100644 --- a/osu.Game/Rulesets/Mods/ModHalfTime.cs +++ b/osu.Game/Rulesets/Mods/ModHalfTime.cs @@ -8,7 +8,7 @@ using osu.Game.Graphics; namespace osu.Game.Rulesets.Mods { - public abstract class ModHalfTime : ModTimeAdjust, IApplicableToClock + public abstract class ModHalfTime : ModTimeAdjust { public override string Name => "Half Time"; public override string Acronym => "HT"; diff --git a/osu.Game/Rulesets/Mods/ModNightcore.cs b/osu.Game/Rulesets/Mods/ModNightcore.cs index dc0fc33088..a4f1ef5a72 100644 --- a/osu.Game/Rulesets/Mods/ModNightcore.cs +++ b/osu.Game/Rulesets/Mods/ModNightcore.cs @@ -1,9 +1,8 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using osu.Framework.Audio; +using osu.Framework.Audio.Track; using osu.Framework.Graphics.Sprites; -using osu.Framework.Timing; using osu.Game.Graphics; namespace osu.Game.Rulesets.Mods @@ -15,12 +14,9 @@ namespace osu.Game.Rulesets.Mods public override IconUsage Icon => OsuIcon.ModNightcore; public override string Description => "Uguuuuuuuu..."; - public override void ApplyToClock(IAdjustableClock clock) + public override void ApplyToTrack(Track track) { - if (clock is IHasPitchAdjust pitchAdjust) - pitchAdjust.PitchAdjust *= RateAdjust; - else - base.ApplyToClock(clock); + track.Frequency.Value *= RateAdjust; } } } diff --git a/osu.Game/Rulesets/Mods/ModTimeAdjust.cs b/osu.Game/Rulesets/Mods/ModTimeAdjust.cs index 513883f552..f137a75ed8 100644 --- a/osu.Game/Rulesets/Mods/ModTimeAdjust.cs +++ b/osu.Game/Rulesets/Mods/ModTimeAdjust.cs @@ -2,23 +2,19 @@ // See the LICENCE file in the repository root for full licence text. using System; -using osu.Framework.Audio; -using osu.Framework.Timing; +using osu.Framework.Audio.Track; namespace osu.Game.Rulesets.Mods { - public abstract class ModTimeAdjust : Mod + public abstract class ModTimeAdjust : Mod, IApplicableToTrack { public override Type[] IncompatibleMods => new[] { typeof(ModTimeRamp) }; protected abstract double RateAdjust { get; } - public virtual void ApplyToClock(IAdjustableClock clock) + public virtual void ApplyToTrack(Track track) { - if (clock is IHasTempoAdjust tempo) - tempo.TempoAdjust *= RateAdjust; - else - clock.Rate *= RateAdjust; + track.TempoAdjust *= RateAdjust; } } } diff --git a/osu.Game/Rulesets/Mods/ModTimeRamp.cs b/osu.Game/Rulesets/Mods/ModTimeRamp.cs index e231225e3c..d95d354487 100644 --- a/osu.Game/Rulesets/Mods/ModTimeRamp.cs +++ b/osu.Game/Rulesets/Mods/ModTimeRamp.cs @@ -3,15 +3,14 @@ using System; using System.Linq; -using osu.Framework.Audio; -using osu.Framework.Timing; +using osu.Framework.Audio.Track; using osu.Game.Beatmaps; using osu.Game.Rulesets.UI; using osu.Game.Rulesets.Objects; namespace osu.Game.Rulesets.Mods { - public abstract class ModTimeRamp : Mod, IUpdatableByPlayfield, IApplicableToClock, IApplicableToBeatmap + public abstract class ModTimeRamp : Mod, IUpdatableByPlayfield, IApplicableToTrack, IApplicableToBeatmap { /// /// The point in the beatmap at which the final ramping rate should be reached. @@ -24,11 +23,11 @@ namespace osu.Game.Rulesets.Mods private double finalRateTime; private double beginRampTime; - private IAdjustableClock clock; + private Track track; - public virtual void ApplyToClock(IAdjustableClock clock) + public virtual void ApplyToTrack(Track track) { - this.clock = clock; + this.track = track; lastAdjust = 1; @@ -46,7 +45,7 @@ namespace osu.Game.Rulesets.Mods public virtual void Update(Playfield playfield) { - applyAdjustment((clock.CurrentTime - beginRampTime) / finalRateTime); + applyAdjustment((track.CurrentTime - beginRampTime) / finalRateTime); } private double lastAdjust = 1; @@ -59,23 +58,8 @@ namespace osu.Game.Rulesets.Mods { double adjust = 1 + (Math.Sign(FinalRateAdjustment) * Math.Clamp(amount, 0, 1) * Math.Abs(FinalRateAdjustment)); - switch (clock) - { - case IHasPitchAdjust pitch: - pitch.PitchAdjust /= lastAdjust; - pitch.PitchAdjust *= adjust; - break; - - case IHasTempoAdjust tempo: - tempo.TempoAdjust /= lastAdjust; - tempo.TempoAdjust *= adjust; - break; - - default: - clock.Rate /= lastAdjust; - clock.Rate *= adjust; - break; - } + track.Tempo.Value /= lastAdjust; + track.Tempo.Value *= lastAdjust; lastAdjust = adjust; } diff --git a/osu.Game/Screens/Play/GameplayClockContainer.cs b/osu.Game/Screens/Play/GameplayClockContainer.cs index 58c9a6a784..1508758c87 100644 --- a/osu.Game/Screens/Play/GameplayClockContainer.cs +++ b/osu.Game/Screens/Play/GameplayClockContainer.cs @@ -28,9 +28,9 @@ namespace osu.Game.Screens.Play private readonly IReadOnlyList mods; /// - /// The original source (usually a 's track). + /// The 's track. /// - private IAdjustableClock sourceClock; + private Track track; public readonly BindableBool IsPaused = new BindableBool(); @@ -72,8 +72,8 @@ namespace osu.Game.Screens.Play RelativeSizeAxes = Axes.Both; - sourceClock = (IAdjustableClock)beatmap.Track ?? new StopwatchClock(); - (sourceClock as IAdjustableAudioComponent)?.AddAdjustment(AdjustableProperty.Frequency, pauseFreqAdjust); + track = beatmap.Track; + track.AddAdjustment(AdjustableProperty.Frequency, pauseFreqAdjust); adjustableClock = new DecoupleableInterpolatingFramedClock { IsCoupled = false }; @@ -127,11 +127,11 @@ namespace osu.Game.Screens.Play { Task.Run(() => { - sourceClock.Reset(); + track.Reset(); Schedule(() => { - adjustableClock.ChangeSource(sourceClock); + adjustableClock.ChangeSource(track); updateRate(); if (!IsPaused.Value) @@ -197,13 +197,13 @@ namespace osu.Game.Screens.Play /// public void StopUsingBeatmapClock() { - if (sourceClock != beatmap.Track) + if (track != beatmap.Track) return; removeSourceClockAdjustments(); - sourceClock = new TrackVirtual(beatmap.Track.Length); - adjustableClock.ChangeSource(sourceClock); + track = new TrackVirtual(beatmap.Track.Length); + adjustableClock.ChangeSource(track); } protected override void Update() @@ -218,18 +218,15 @@ namespace osu.Game.Screens.Play private void updateRate() { - if (sourceClock == null) return; + if (track == null) return; speedAdjustmentsApplied = true; - sourceClock.ResetSpeedAdjustments(); + track.ResetSpeedAdjustments(); - if (sourceClock is IHasTempoAdjust tempo) - tempo.TempoAdjust = UserPlaybackRate.Value; - else - sourceClock.Rate = UserPlaybackRate.Value; + track.Tempo.Value = UserPlaybackRate.Value; - foreach (var mod in mods.OfType()) - mod.ApplyToClock(sourceClock); + foreach (var mod in mods.OfType()) + mod.ApplyToTrack(track); } protected override void Dispose(bool isDisposing) @@ -237,18 +234,18 @@ namespace osu.Game.Screens.Play base.Dispose(isDisposing); removeSourceClockAdjustments(); - sourceClock = null; + track = null; } private void removeSourceClockAdjustments() { if (speedAdjustmentsApplied) { - sourceClock.ResetSpeedAdjustments(); + track.ResetSpeedAdjustments(); speedAdjustmentsApplied = false; } - (sourceClock as IAdjustableAudioComponent)?.RemoveAdjustment(AdjustableProperty.Frequency, pauseFreqAdjust); + (track as IAdjustableAudioComponent)?.RemoveAdjustment(AdjustableProperty.Frequency, pauseFreqAdjust); } } } From 5a093c039cedd3b214827871a77c3ef6c69852c7 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 9 Dec 2019 17:45:08 +0900 Subject: [PATCH 63/89] Simplify path/point construction --- osu.Game/Rulesets/Objects/PathControlPoint.cs | 15 ++++++++++++++- osu.Game/Rulesets/Objects/SliderPath.cs | 15 ++++----------- 2 files changed, 18 insertions(+), 12 deletions(-) diff --git a/osu.Game/Rulesets/Objects/PathControlPoint.cs b/osu.Game/Rulesets/Objects/PathControlPoint.cs index 83436b7a36..de40c24060 100644 --- a/osu.Game/Rulesets/Objects/PathControlPoint.cs +++ b/osu.Game/Rulesets/Objects/PathControlPoint.cs @@ -26,12 +26,25 @@ namespace osu.Game.Rulesets.Objects /// internal event Action Changed; + /// + /// Creates a new . + /// public PathControlPoint() + : this(Vector2.Zero, null) + { + } + + /// + /// Creates a new with a provided position and type. + /// + /// The initial position. + /// The initial type. + public PathControlPoint(Vector2 position, PathType? type = null) { Position.ValueChanged += _ => Changed?.Invoke(); Type.ValueChanged += _ => Changed?.Invoke(); } - public bool Equals(PathControlPoint other) => Position.Value == other.Position.Value && Type.Value == other.Type.Value; + public bool Equals(PathControlPoint other) => Position.Value == other?.Position.Value && Type.Value == other.Type.Value; } } diff --git a/osu.Game/Rulesets/Objects/SliderPath.cs b/osu.Game/Rulesets/Objects/SliderPath.cs index 969cdcc463..dbd236107c 100644 --- a/osu.Game/Rulesets/Objects/SliderPath.cs +++ b/osu.Game/Rulesets/Objects/SliderPath.cs @@ -40,12 +40,10 @@ namespace osu.Game.Rulesets.Objects /// public readonly BindableList ControlPoints = new BindableList(); - public readonly List Test = new List(); - - private readonly Cached pathCache = new Cached(); - private readonly List calculatedPath = new List(); private readonly List cumulativeLength = new List(); + private readonly Cached pathCache = new Cached(); + private double calculatedLength; /// @@ -87,7 +85,7 @@ namespace osu.Game.Rulesets.Objects } /// - /// Creates a new . + /// Creates a new initialised with a list of control points. /// /// An optional set of s to initialise the path with. /// A user-set distance of the path that may be shorter or longer than the true distance between all control points. @@ -101,13 +99,8 @@ namespace osu.Game.Rulesets.Objects } public SliderPath(PathType type, Vector2[] controlPoints, double? expectedDistance = null) - : this() + : this(controlPoints.Select((c, i) => new PathControlPoint(c, i == 0 ? (PathType?)type : null)).ToArray(), expectedDistance) { - foreach (var c in controlPoints) - ControlPoints.Add(new PathControlPoint { Position = { Value = c } }); - ControlPoints[0].Type.Value = type; - - ExpectedDistance.Value = expectedDistance; } /// From 9cb649436c838047fdddbc91b87f08f07e94afd2 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 9 Dec 2019 17:47:05 +0900 Subject: [PATCH 64/89] Default to linear control point type --- osu.Game/Rulesets/Objects/SliderPath.cs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/osu.Game/Rulesets/Objects/SliderPath.cs b/osu.Game/Rulesets/Objects/SliderPath.cs index dbd236107c..e323054234 100644 --- a/osu.Game/Rulesets/Objects/SliderPath.cs +++ b/osu.Game/Rulesets/Objects/SliderPath.cs @@ -196,9 +196,6 @@ namespace osu.Game.Rulesets.Objects if (ControlPoints.Count == 0) return; - if (ControlPoints[0].Type.Value == null) - throw new InvalidOperationException($"The first control point in a {nameof(SliderPath)} must have a non-null type."); - Vector2[] vertices = new Vector2[ControlPoints.Count]; for (int i = 0; i < ControlPoints.Count; i++) vertices[i] = ControlPoints[i].Position.Value; @@ -214,7 +211,7 @@ namespace osu.Game.Rulesets.Objects // The current vertex ends the segment var segmentVertices = vertices.AsSpan().Slice(start, i - start + 1); - var segmentType = ControlPoints[start].Type.Value.Value; + var segmentType = ControlPoints[start].Type.Value ?? PathType.Linear; foreach (Vector2 t in computeSubPath(segmentVertices, segmentType)) { From fa1468325e504a22f7a6e7f9dff5c34dd8878d41 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 9 Dec 2019 17:48:27 +0900 Subject: [PATCH 65/89] Refactor hitobjects to remove default control point --- osu.Game.Rulesets.Catch/Objects/JuiceStream.cs | 2 +- osu.Game.Rulesets.Osu/Objects/Slider.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs b/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs index 366f10d61d..d5d99640af 100644 --- a/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs +++ b/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs @@ -116,7 +116,7 @@ namespace osu.Game.Rulesets.Catch.Objects public double Duration => EndTime - StartTime; - private readonly SliderPath path = new SliderPath(new[] { new PathControlPoint { Type = { Value = PathType.Linear } } }); + private readonly SliderPath path = new SliderPath(); public SliderPath Path { diff --git a/osu.Game.Rulesets.Osu/Objects/Slider.cs b/osu.Game.Rulesets.Osu/Objects/Slider.cs index 4c299fd7c2..134576316a 100644 --- a/osu.Game.Rulesets.Osu/Objects/Slider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Slider.cs @@ -27,7 +27,7 @@ namespace osu.Game.Rulesets.Osu.Objects public Vector2 StackedPositionAt(double t) => StackedPosition + this.CurvePositionAt(t); - private readonly SliderPath path = new SliderPath(new[] { new PathControlPoint { Type = { Value = PathType.Linear } } }); + private readonly SliderPath path = new SliderPath(); public SliderPath Path { From 883d5bc11d54cfd72e58f2e7d42c21b8fea1141b Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 9 Dec 2019 17:54:19 +0900 Subject: [PATCH 66/89] Remove automatic slider path offsetting --- .../Components/PathControlPointVisualiser.cs | 8 ++++++++ osu.Game.Rulesets.Osu/Objects/Slider.cs | 2 -- osu.Game/Rulesets/Objects/SliderPath.cs | 20 ------------------- 3 files changed, 8 insertions(+), 22 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs index f0888b34fe..434e74ddeb 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs @@ -16,6 +16,7 @@ using osu.Game.Graphics.UserInterface; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Screens.Edit.Compose; +using osuTK; using osuTK.Input; namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components @@ -122,6 +123,13 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components return true; } + // The path will have a non-zero offset if the head is removed, but sliders don't support this behaviour since the head is positioned at the slider's position + // So the slider needs to be offset by this amount instead, and all control points offset backwards such that the path is re-positioned at (0, 0) + Vector2 first = slider.Path.ControlPoints[0].Position.Value; + foreach (var c in slider.Path.ControlPoints) + c.Position.Value -= first; + slider.Position += first; + // Since pieces are re-used, they will not point to the deleted control points while remaining selected foreach (var piece in Pieces) piece.IsSelected.Value = false; diff --git a/osu.Game.Rulesets.Osu/Objects/Slider.cs b/osu.Game.Rulesets.Osu/Objects/Slider.cs index 134576316a..34e5a7f3cd 100644 --- a/osu.Game.Rulesets.Osu/Objects/Slider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Slider.cs @@ -113,8 +113,6 @@ namespace osu.Game.Rulesets.Osu.Objects { SamplesBindable.ItemsAdded += _ => updateNestedSamples(); SamplesBindable.ItemsRemoved += _ => updateNestedSamples(); - - Path.OffsetChanged += offset => Position += offset; Path.Version.ValueChanged += _ => updateNestedPositions(); } diff --git a/osu.Game/Rulesets/Objects/SliderPath.cs b/osu.Game/Rulesets/Objects/SliderPath.cs index e323054234..d868ee27f0 100644 --- a/osu.Game/Rulesets/Objects/SliderPath.cs +++ b/osu.Game/Rulesets/Objects/SliderPath.cs @@ -16,12 +16,6 @@ namespace osu.Game.Rulesets.Objects { public class SliderPath { - /// - /// Invoked when the offset of the path changes. - /// The provided value indicates the offset, and should be used to re-calculate the position of the containing drawable. - /// - public event Action OffsetChanged; - /// /// The current version of this . Updated when any change to the path occurs. /// @@ -66,20 +60,6 @@ namespace osu.Game.Rulesets.Objects foreach (var c in items) c.Changed -= invalidate; - // Make all control points relative to the first one - if (ControlPoints.Count > 0) - { - Vector2 first = ControlPoints[0].Position.Value; - - if (first != Vector2.Zero) - { - foreach (var c in ControlPoints) - c.Position.Value -= first; - - OffsetChanged?.Invoke(first); - } - } - invalidate(); }; } From bfbb9aa18e09640549f580748be35a5e7690d82b Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 9 Dec 2019 18:01:02 +0900 Subject: [PATCH 67/89] Remove outdated assert --- osu.Game/Rulesets/Objects/SliderPath.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game/Rulesets/Objects/SliderPath.cs b/osu.Game/Rulesets/Objects/SliderPath.cs index d868ee27f0..d5ae6f471d 100644 --- a/osu.Game/Rulesets/Objects/SliderPath.cs +++ b/osu.Game/Rulesets/Objects/SliderPath.cs @@ -187,8 +187,6 @@ namespace osu.Game.Rulesets.Objects if (ControlPoints[i].Type.Value == null && i < ControlPoints.Count - 1) continue; - Debug.Assert(ControlPoints[start].Type.Value.HasValue); - // The current vertex ends the segment var segmentVertices = vertices.AsSpan().Slice(start, i - start + 1); var segmentType = ControlPoints[start].Type.Value ?? PathType.Linear; From a1798fd38d29e8cc25a0919025b78bd4e1f24e39 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 9 Dec 2019 18:01:13 +0900 Subject: [PATCH 68/89] Fix bad ctor implementation --- osu.Game/Rulesets/Objects/PathControlPoint.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Rulesets/Objects/PathControlPoint.cs b/osu.Game/Rulesets/Objects/PathControlPoint.cs index de40c24060..5737d3f618 100644 --- a/osu.Game/Rulesets/Objects/PathControlPoint.cs +++ b/osu.Game/Rulesets/Objects/PathControlPoint.cs @@ -41,6 +41,9 @@ namespace osu.Game.Rulesets.Objects /// The initial type. public PathControlPoint(Vector2 position, PathType? type = null) { + Position.Value = position; + Type.Value = type; + Position.ValueChanged += _ => Changed?.Invoke(); Type.ValueChanged += _ => Changed?.Invoke(); } From d650bfb6d6d3c56439c3a56e1c1afd940d9d989b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 9 Dec 2019 18:05:14 +0900 Subject: [PATCH 69/89] Remove unnecessary cast --- osu.Game/Screens/Play/GameplayClockContainer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/GameplayClockContainer.cs b/osu.Game/Screens/Play/GameplayClockContainer.cs index 1508758c87..2cc03ae453 100644 --- a/osu.Game/Screens/Play/GameplayClockContainer.cs +++ b/osu.Game/Screens/Play/GameplayClockContainer.cs @@ -245,7 +245,7 @@ namespace osu.Game.Screens.Play speedAdjustmentsApplied = false; } - (track as IAdjustableAudioComponent)?.RemoveAdjustment(AdjustableProperty.Frequency, pauseFreqAdjust); + track.RemoveAdjustment(AdjustableProperty.Frequency, pauseFreqAdjust); } } } From 2dbf94f3abc054504d74a831fc8e885640710d1b Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 9 Dec 2019 18:10:33 +0900 Subject: [PATCH 70/89] Make placement blueprint add an initial segment --- .../Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs index 7dd2017e48..c004b6db28 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs @@ -30,7 +30,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders private PlacementState state; private PathControlPoint segmentStart; private PathControlPoint cursor; - private int currentSegmentLength = 1; + private int currentSegmentLength; [Resolved(CanBeNull = true)] private HitObjectComposer composer { get; set; } @@ -40,7 +40,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders { RelativeSizeAxes = Axes.Both; - segmentStart = HitObject.Path.ControlPoints[0]; + HitObject.Path.ControlPoints.Add(segmentStart = new PathControlPoint(Vector2.Zero, PathType.Linear)); + currentSegmentLength = 1; } [BackgroundDependencyLoader] From b6e2738236da446fd99de6f44fa6cb5930572a3f Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 9 Dec 2019 18:15:00 +0900 Subject: [PATCH 71/89] Remove unused using --- osu.Game/Rulesets/Objects/SliderPath.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Rulesets/Objects/SliderPath.cs b/osu.Game/Rulesets/Objects/SliderPath.cs index d5ae6f471d..d8c6320c6d 100644 --- a/osu.Game/Rulesets/Objects/SliderPath.cs +++ b/osu.Game/Rulesets/Objects/SliderPath.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; -using System.Diagnostics; using System.Linq; using Newtonsoft.Json; using osu.Framework.Bindables; From 6d9cd0fafe6b405d48fe74061c529bcdf0aaeeac Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 9 Dec 2019 18:25:13 +0900 Subject: [PATCH 72/89] Split out complex method --- osu.Game/Rulesets/Objects/SliderPath.cs | 38 ++++++++++++------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/osu.Game/Rulesets/Objects/SliderPath.cs b/osu.Game/Rulesets/Objects/SliderPath.cs index d8c6320c6d..6a151d7d33 100644 --- a/osu.Game/Rulesets/Objects/SliderPath.cs +++ b/osu.Game/Rulesets/Objects/SliderPath.cs @@ -190,7 +190,7 @@ namespace osu.Game.Rulesets.Objects var segmentVertices = vertices.AsSpan().Slice(start, i - start + 1); var segmentType = ControlPoints[start].Type.Value ?? PathType.Linear; - foreach (Vector2 t in computeSubPath(segmentVertices, segmentType)) + foreach (Vector2 t in calculateSubPath(segmentVertices, segmentType)) { if (calculatedPath.Count == 0 || calculatedPath.Last() != t) calculatedPath.Add(t); @@ -199,32 +199,32 @@ namespace osu.Game.Rulesets.Objects // Start the new segment at the current vertex start = i; } + } - static List computeSubPath(ReadOnlySpan subControlPoints, PathType type) + private List calculateSubPath(ReadOnlySpan subControlPoints, PathType type) + { + switch (type) { - switch (type) - { - case PathType.Linear: - return PathApproximator.ApproximateLinear(subControlPoints); + case PathType.Linear: + return PathApproximator.ApproximateLinear(subControlPoints); - case PathType.PerfectCurve: - if (subControlPoints.Length != 3) - break; + case PathType.PerfectCurve: + if (subControlPoints.Length != 3) + break; - List subpath = PathApproximator.ApproximateCircularArc(subControlPoints); + List subpath = PathApproximator.ApproximateCircularArc(subControlPoints); - // If for some reason a circular arc could not be fit to the 3 given points, fall back to a numerically stable bezier approximation. - if (subpath.Count == 0) - break; + // If for some reason a circular arc could not be fit to the 3 given points, fall back to a numerically stable bezier approximation. + if (subpath.Count == 0) + break; - return subpath; + return subpath; - case PathType.Catmull: - return PathApproximator.ApproximateCatmull(subControlPoints); - } - - return PathApproximator.ApproximateBezier(subControlPoints); + case PathType.Catmull: + return PathApproximator.ApproximateCatmull(subControlPoints); } + + return PathApproximator.ApproximateBezier(subControlPoints); } private void calculateLength() From 04b3297a0515c9a204109b387bf4e5e3008cabe8 Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Fri, 6 Dec 2019 21:32:31 +0800 Subject: [PATCH 73/89] Constrain configuration lookup as enum. --- osu.Game/Configuration/DatabasedConfigManager.cs | 7 ++++--- osu.Game/Configuration/InMemoryConfigManager.cs | 5 +++-- osu.Game/Rulesets/Configuration/RulesetConfigManager.cs | 5 +++-- osu.Game/Skinning/SkinConfigManager.cs | 3 ++- 4 files changed, 12 insertions(+), 8 deletions(-) diff --git a/osu.Game/Configuration/DatabasedConfigManager.cs b/osu.Game/Configuration/DatabasedConfigManager.cs index 1ef4c2527a..b3783b45a8 100644 --- a/osu.Game/Configuration/DatabasedConfigManager.cs +++ b/osu.Game/Configuration/DatabasedConfigManager.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using System.Collections.Generic; using System.Linq; using osu.Framework.Bindables; @@ -9,8 +10,8 @@ using osu.Game.Rulesets; namespace osu.Game.Configuration { - public abstract class DatabasedConfigManager : ConfigManager - where T : struct + public abstract class DatabasedConfigManager : ConfigManager + where TLookup : struct, Enum { private readonly SettingsStore settings; @@ -53,7 +54,7 @@ namespace osu.Game.Configuration private readonly List dirtySettings = new List(); - protected override void AddBindable(T lookup, Bindable bindable) + protected override void AddBindable(TLookup lookup, Bindable bindable) { base.AddBindable(lookup, bindable); diff --git a/osu.Game/Configuration/InMemoryConfigManager.cs b/osu.Game/Configuration/InMemoryConfigManager.cs index b0dc6b0e9c..ccf697f680 100644 --- a/osu.Game/Configuration/InMemoryConfigManager.cs +++ b/osu.Game/Configuration/InMemoryConfigManager.cs @@ -1,12 +1,13 @@ // 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.Configuration; namespace osu.Game.Configuration { - public class InMemoryConfigManager : ConfigManager - where T : struct + public class InMemoryConfigManager : ConfigManager + where TLookup : struct, Enum { public InMemoryConfigManager() { diff --git a/osu.Game/Rulesets/Configuration/RulesetConfigManager.cs b/osu.Game/Rulesets/Configuration/RulesetConfigManager.cs index ed5fdf9809..0ff3455f00 100644 --- a/osu.Game/Rulesets/Configuration/RulesetConfigManager.cs +++ b/osu.Game/Rulesets/Configuration/RulesetConfigManager.cs @@ -1,12 +1,13 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using osu.Game.Configuration; namespace osu.Game.Rulesets.Configuration { - public abstract class RulesetConfigManager : DatabasedConfigManager, IRulesetConfigManager - where T : struct + public abstract class RulesetConfigManager : DatabasedConfigManager, IRulesetConfigManager + where TLookup : struct, Enum { protected RulesetConfigManager(SettingsStore settings, RulesetInfo ruleset, int? variant = null) : base(settings, ruleset, variant) diff --git a/osu.Game/Skinning/SkinConfigManager.cs b/osu.Game/Skinning/SkinConfigManager.cs index 896444d1d2..682138a2e9 100644 --- a/osu.Game/Skinning/SkinConfigManager.cs +++ b/osu.Game/Skinning/SkinConfigManager.cs @@ -1,11 +1,12 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using osu.Framework.Configuration; namespace osu.Game.Skinning { - public class SkinConfigManager : ConfigManager where T : struct + public class SkinConfigManager : ConfigManager where TLookup : struct, Enum { protected override void PerformLoad() { From 40a5c1fd96bfd197131f15bc6c9eececba82c437 Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Sat, 7 Dec 2019 19:49:52 +0800 Subject: [PATCH 74/89] Constrain transformable with class. --- osu.Game/Graphics/IHasAccentColour.cs | 2 +- osu.Game/Storyboards/Drawables/IFlippable.cs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Graphics/IHasAccentColour.cs b/osu.Game/Graphics/IHasAccentColour.cs index 1a66819379..af497da70f 100644 --- a/osu.Game/Graphics/IHasAccentColour.cs +++ b/osu.Game/Graphics/IHasAccentColour.cs @@ -24,7 +24,7 @@ namespace osu.Game.Graphics /// /// A to which further transforms can be added. public static TransformSequence FadeAccent(this T accentedDrawable, Color4 newColour, double duration = 0, Easing easing = Easing.None) - where T : IHasAccentColour + where T : class, IHasAccentColour => accentedDrawable.TransformTo(nameof(accentedDrawable.AccentColour), newColour, duration, easing); /// diff --git a/osu.Game/Storyboards/Drawables/IFlippable.cs b/osu.Game/Storyboards/Drawables/IFlippable.cs index 9e12de5833..1c4cdde22d 100644 --- a/osu.Game/Storyboards/Drawables/IFlippable.cs +++ b/osu.Game/Storyboards/Drawables/IFlippable.cs @@ -41,7 +41,7 @@ namespace osu.Game.Storyboards.Drawables /// /// A to which further transforms can be added. public static TransformSequence TransformFlipH(this T flippable, bool newValue, double delay = 0) - where T : IFlippable + where T : class, IFlippable => flippable.TransformTo(flippable.PopulateTransform(new TransformFlipH(), newValue, delay)); /// @@ -49,7 +49,7 @@ namespace osu.Game.Storyboards.Drawables /// /// A to which further transforms can be added. public static TransformSequence TransformFlipV(this T flippable, bool newValue, double delay = 0) - where T : IFlippable + where T : class, IFlippable => flippable.TransformTo(flippable.PopulateTransform(new TransformFlipV(), newValue, delay)); } } From c3518a2b9491960e573e5de0e3860d66a178a0c7 Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Sat, 7 Dec 2019 19:56:56 +0800 Subject: [PATCH 75/89] Enum constraint for enum dropdown. --- .../Graphics/UserInterface/OsuEnumDropdown.cs | 4 +--- .../SearchableList/DisplayStyleControl.cs | 2 ++ .../SearchableListFilterControl.cs | 19 +++++++++---------- .../SearchableList/SearchableListHeader.cs | 4 +--- .../SearchableList/SearchableListOverlay.cs | 14 +++++++++----- .../SearchableList/SlimEnumDropdown.cs | 2 ++ .../Overlays/Settings/SettingsEnumDropdown.cs | 2 ++ 7 files changed, 26 insertions(+), 21 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/OsuEnumDropdown.cs b/osu.Game/Graphics/UserInterface/OsuEnumDropdown.cs index e132027787..528d7d60f8 100644 --- a/osu.Game/Graphics/UserInterface/OsuEnumDropdown.cs +++ b/osu.Game/Graphics/UserInterface/OsuEnumDropdown.cs @@ -6,12 +6,10 @@ using System; namespace osu.Game.Graphics.UserInterface { public class OsuEnumDropdown : OsuDropdown + where T : struct, Enum { public OsuEnumDropdown() { - if (!typeof(T).IsEnum) - throw new InvalidOperationException("OsuEnumDropdown only supports enums as the generic type argument"); - Items = (T[])Enum.GetValues(typeof(T)); } } diff --git a/osu.Game/Overlays/SearchableList/DisplayStyleControl.cs b/osu.Game/Overlays/SearchableList/DisplayStyleControl.cs index 0808cc8fcc..a33f4eb30d 100644 --- a/osu.Game/Overlays/SearchableList/DisplayStyleControl.cs +++ b/osu.Game/Overlays/SearchableList/DisplayStyleControl.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 osu.Framework.Bindables; using osuTK; using osu.Framework.Graphics; @@ -11,6 +12,7 @@ using osu.Game.Graphics.Containers; namespace osu.Game.Overlays.SearchableList { public class DisplayStyleControl : Container + where T : struct, Enum { public readonly SlimEnumDropdown Dropdown; public readonly Bindable DisplayStyle = new Bindable(); diff --git a/osu.Game/Overlays/SearchableList/SearchableListFilterControl.cs b/osu.Game/Overlays/SearchableList/SearchableListFilterControl.cs index 372da94b37..117f905de4 100644 --- a/osu.Game/Overlays/SearchableList/SearchableListFilterControl.cs +++ b/osu.Game/Overlays/SearchableList/SearchableListFilterControl.cs @@ -13,7 +13,9 @@ using osu.Framework.Graphics.Shapes; namespace osu.Game.Overlays.SearchableList { - public abstract class SearchableListFilterControl : Container + public abstract class SearchableListFilterControl : Container + where TTab : struct, Enum + where TCategory : struct, Enum { private const float padding = 10; @@ -21,12 +23,12 @@ namespace osu.Game.Overlays.SearchableList private readonly Box tabStrip; public readonly SearchTextBox Search; - public readonly PageTabControl Tabs; - public readonly DisplayStyleControl DisplayStyleControl; + public readonly PageTabControl Tabs; + public readonly DisplayStyleControl DisplayStyleControl; protected abstract Color4 BackgroundColour { get; } - protected abstract T DefaultTab { get; } - protected abstract U DefaultCategory { get; } + protected abstract TTab DefaultTab { get; } + protected abstract TCategory DefaultCategory { get; } protected virtual Drawable CreateSupplementaryControls() => null; /// @@ -36,9 +38,6 @@ namespace osu.Game.Overlays.SearchableList protected SearchableListFilterControl() { - if (!typeof(T).IsEnum) - throw new InvalidOperationException("SearchableListFilterControl's sort tabs only support enums as the generic type argument"); - RelativeSizeAxes = Axes.X; var controls = CreateSupplementaryControls(); @@ -90,7 +89,7 @@ namespace osu.Game.Overlays.SearchableList RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, Padding = new MarginPadding { Right = 225 }, - Child = Tabs = new PageTabControl + Child = Tabs = new PageTabControl { RelativeSizeAxes = Axes.X, }, @@ -105,7 +104,7 @@ namespace osu.Game.Overlays.SearchableList }, }, }, - DisplayStyleControl = new DisplayStyleControl + DisplayStyleControl = new DisplayStyleControl { Anchor = Anchor.TopRight, Origin = Anchor.TopRight, diff --git a/osu.Game/Overlays/SearchableList/SearchableListHeader.cs b/osu.Game/Overlays/SearchableList/SearchableListHeader.cs index 73dca956d1..66fedf0a56 100644 --- a/osu.Game/Overlays/SearchableList/SearchableListHeader.cs +++ b/osu.Game/Overlays/SearchableList/SearchableListHeader.cs @@ -14,6 +14,7 @@ using osu.Framework.Graphics.Sprites; namespace osu.Game.Overlays.SearchableList { public abstract class SearchableListHeader : Container + where T : struct, Enum { public readonly HeaderTabControl Tabs; @@ -24,9 +25,6 @@ namespace osu.Game.Overlays.SearchableList protected SearchableListHeader() { - if (!typeof(T).IsEnum) - throw new InvalidOperationException("BrowseHeader only supports enums as the generic type argument"); - RelativeSizeAxes = Axes.X; Height = 90; diff --git a/osu.Game/Overlays/SearchableList/SearchableListOverlay.cs b/osu.Game/Overlays/SearchableList/SearchableListOverlay.cs index fb0c1d9808..37478d902b 100644 --- a/osu.Game/Overlays/SearchableList/SearchableListOverlay.cs +++ b/osu.Game/Overlays/SearchableList/SearchableListOverlay.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 osuTK.Graphics; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -16,19 +17,22 @@ namespace osu.Game.Overlays.SearchableList public const float WIDTH_PADDING = 80; } - public abstract class SearchableListOverlay : SearchableListOverlay + public abstract class SearchableListOverlay : SearchableListOverlay + where THeader : struct, Enum + where TTab : struct, Enum + where TCategory : struct, Enum { private readonly Container scrollContainer; - protected readonly SearchableListHeader Header; - protected readonly SearchableListFilterControl Filter; + protected readonly SearchableListHeader Header; + protected readonly SearchableListFilterControl Filter; protected readonly FillFlowContainer ScrollFlow; protected abstract Color4 BackgroundColour { get; } protected abstract Color4 TrianglesColourLight { get; } protected abstract Color4 TrianglesColourDark { get; } - protected abstract SearchableListHeader CreateHeader(); - protected abstract SearchableListFilterControl CreateFilterControl(); + protected abstract SearchableListHeader CreateHeader(); + protected abstract SearchableListFilterControl CreateFilterControl(); protected SearchableListOverlay() { diff --git a/osu.Game/Overlays/SearchableList/SlimEnumDropdown.cs b/osu.Game/Overlays/SearchableList/SlimEnumDropdown.cs index f320ef1344..9e7ff1205f 100644 --- a/osu.Game/Overlays/SearchableList/SlimEnumDropdown.cs +++ b/osu.Game/Overlays/SearchableList/SlimEnumDropdown.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 osuTK.Graphics; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; @@ -11,6 +12,7 @@ using osuTK; namespace osu.Game.Overlays.SearchableList { public class SlimEnumDropdown : OsuEnumDropdown + where T : struct, Enum { protected override DropdownHeader CreateHeader() => new SlimDropdownHeader(); diff --git a/osu.Game/Overlays/Settings/SettingsEnumDropdown.cs b/osu.Game/Overlays/Settings/SettingsEnumDropdown.cs index 9f09f251c2..c77d14632b 100644 --- a/osu.Game/Overlays/Settings/SettingsEnumDropdown.cs +++ b/osu.Game/Overlays/Settings/SettingsEnumDropdown.cs @@ -1,12 +1,14 @@ // 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.Game.Graphics.UserInterface; namespace osu.Game.Overlays.Settings { public class SettingsEnumDropdown : SettingsDropdown + where T : struct, Enum { protected override OsuDropdown CreateDropdown() => new DropdownControl(); From ad1fb3bda2b75bb357d4bd2b8930af7d533e3384 Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Mon, 9 Dec 2019 17:48:41 +0800 Subject: [PATCH 76/89] Remove IComparable in constraint. --- osu.Game/Screens/Select/FilterCriteria.cs | 2 +- osu.Game/Screens/Select/FilterQueryParser.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Select/FilterCriteria.cs b/osu.Game/Screens/Select/FilterCriteria.cs index abcb1f2171..e3ad76ac35 100644 --- a/osu.Game/Screens/Select/FilterCriteria.cs +++ b/osu.Game/Screens/Select/FilterCriteria.cs @@ -44,7 +44,7 @@ namespace osu.Game.Screens.Select } public struct OptionalRange : IEquatable> - where T : struct, IComparable + where T : struct { public bool HasFilter => Max != null || Min != null; diff --git a/osu.Game/Screens/Select/FilterQueryParser.cs b/osu.Game/Screens/Select/FilterQueryParser.cs index ffe1258168..89afc729fe 100644 --- a/osu.Game/Screens/Select/FilterQueryParser.cs +++ b/osu.Game/Screens/Select/FilterQueryParser.cs @@ -170,7 +170,7 @@ namespace osu.Game.Screens.Select } private static void updateCriteriaRange(ref FilterCriteria.OptionalRange range, string op, T value) - where T : struct, IComparable + where T : struct { switch (op) { From 47f3c4a596a3b0b8f54e492dd723644e1665241f Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 9 Dec 2019 20:18:18 +0900 Subject: [PATCH 77/89] Don't serialise path version --- osu.Game/Rulesets/Objects/SliderPath.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Rulesets/Objects/SliderPath.cs b/osu.Game/Rulesets/Objects/SliderPath.cs index 6a151d7d33..86deba3b93 100644 --- a/osu.Game/Rulesets/Objects/SliderPath.cs +++ b/osu.Game/Rulesets/Objects/SliderPath.cs @@ -18,6 +18,7 @@ namespace osu.Game.Rulesets.Objects /// /// The current version of this . Updated when any change to the path occurs. /// + [JsonIgnore] public IBindable Version => version; private readonly Bindable version = new Bindable(); From bd2b0af2695048a1f5baae9cbde9f80a18e28957 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 9 Dec 2019 22:35:19 +0900 Subject: [PATCH 78/89] Consider having only 1 control point as being deleted --- .../Sliders/Components/PathControlPointVisualiser.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs index 434e74ddeb..f7692c64f4 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs @@ -116,8 +116,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components slider.Path.ControlPoints.Remove(c); } - // If there are 0 remaining control points, treat the slider as being deleted - if (slider.Path.ControlPoints.Count == 0) + // If there are 0 or 1 remaining control points, the slider is in a degenerate (single point) form and should be deleted + if (slider.Path.ControlPoints.Count <= 1) { placementHandler?.Delete(slider); return true; From 2c4c190f15238574285407bd7fd5925b22c1c768 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 9 Dec 2019 22:44:47 +0900 Subject: [PATCH 79/89] Fix control points not adding to last segment --- .../Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs index 7431972673..68873093a6 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs @@ -104,7 +104,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders int insertionIndex = 0; float minDistance = float.MaxValue; - for (int i = 0; i < HitObject.Path.ControlPoints.Count - 2; i++) + for (int i = 0; i < HitObject.Path.ControlPoints.Count - 1; i++) { float dist = new Line(HitObject.Path.ControlPoints[i].Position.Value, HitObject.Path.ControlPoints[i + 1].Position.Value).DistanceToPoint(position); From 1e71681916b3fe6473da6f3fda1e2fb35f9dc7fd Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 10 Dec 2019 02:00:39 +0900 Subject: [PATCH 80/89] Fix osu!catch catcher not scaling down correctly --- osu.Game.Rulesets.Catch/UI/CatcherSprite.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Catch/UI/CatcherSprite.cs b/osu.Game.Rulesets.Catch/UI/CatcherSprite.cs index e3c6c93d01..025fa9c56e 100644 --- a/osu.Game.Rulesets.Catch/UI/CatcherSprite.cs +++ b/osu.Game.Rulesets.Catch/UI/CatcherSprite.cs @@ -22,7 +22,7 @@ namespace osu.Game.Rulesets.Catch.UI [BackgroundDependencyLoader] private void load() { - InternalChild = new SkinnableSprite("Gameplay/catch/fruit-catcher-idle") + InternalChild = new SkinnableSprite("Gameplay/catch/fruit-catcher-idle", confineMode: ConfineMode.ScaleDownToFit) { RelativeSizeAxes = Axes.Both, Anchor = Anchor.TopCentre, From dc9775742ca0dfc20f65e9de2d043c714a6f1a61 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 10 Dec 2019 02:23:17 +0900 Subject: [PATCH 81/89] Fix incorrect code migration MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Bartłomiej Dach --- osu.Game/Rulesets/Mods/ModTimeRamp.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Mods/ModTimeRamp.cs b/osu.Game/Rulesets/Mods/ModTimeRamp.cs index d95d354487..839b2ae36e 100644 --- a/osu.Game/Rulesets/Mods/ModTimeRamp.cs +++ b/osu.Game/Rulesets/Mods/ModTimeRamp.cs @@ -59,7 +59,7 @@ namespace osu.Game.Rulesets.Mods double adjust = 1 + (Math.Sign(FinalRateAdjustment) * Math.Clamp(amount, 0, 1) * Math.Abs(FinalRateAdjustment)); track.Tempo.Value /= lastAdjust; - track.Tempo.Value *= lastAdjust; + track.Tempo.Value *= adjust; lastAdjust = adjust; } From cdde5d1d690adf16a02cc8b48cdb85467f68fb61 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 10 Dec 2019 02:30:23 +0900 Subject: [PATCH 82/89] Fix song select filters not reapplied if in a child screen Closes https://github.com/ppy/osu/issues/6980. --- .../SongSelect/TestScenePlaySongSelect.cs | 36 +++++++++++++++++++ .../Visual/TestSceneOsuScreenStack.cs | 2 +- osu.Game/Screens/Select/SongSelect.cs | 8 ++--- osu.Game/Tests/Visual/ScreenTestScene.cs | 10 +++--- 4 files changed, 46 insertions(+), 10 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs index a4b8d1a24a..5dd02c1ddd 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs @@ -95,6 +95,42 @@ namespace osu.Game.Tests.Visual.SongSelect AddAssert("filter count is 1", () => songSelect.FilterCount == 1); } + [Test] + public void TestNoFilterOnSimpleResume() + { + addRulesetImportStep(0); + addRulesetImportStep(0); + + createSongSelect(); + + AddStep("push child screen", () => Stack.Push(new TestSceneOsuScreenStack.TestScreen("test child"))); + AddUntilStep("wait for not current", () => !songSelect.IsCurrentScreen()); + + AddStep("return", () => songSelect.MakeCurrent()); + AddUntilStep("wait for current", () => songSelect.IsCurrentScreen()); + AddAssert("filter count is 1", () => songSelect.FilterCount == 1); + } + + [Test] + public void TestFilterOnResumeAfterChange() + { + addRulesetImportStep(0); + addRulesetImportStep(0); + + AddStep("change convert setting", () => config.Set(OsuSetting.ShowConvertedBeatmaps, false)); + + createSongSelect(); + + AddStep("push child screen", () => Stack.Push(new TestSceneOsuScreenStack.TestScreen("test child"))); + AddUntilStep("wait for not current", () => !songSelect.IsCurrentScreen()); + + AddStep("change convert setting", () => config.Set(OsuSetting.ShowConvertedBeatmaps, true)); + + AddStep("return", () => songSelect.MakeCurrent()); + AddUntilStep("wait for current", () => songSelect.IsCurrentScreen()); + AddAssert("filter count is 2", () => songSelect.FilterCount == 2); + } + [Test] public void TestAudioResuming() { diff --git a/osu.Game.Tests/Visual/TestSceneOsuScreenStack.cs b/osu.Game.Tests/Visual/TestSceneOsuScreenStack.cs index a68fd0ef40..c55988d1bb 100644 --- a/osu.Game.Tests/Visual/TestSceneOsuScreenStack.cs +++ b/osu.Game.Tests/Visual/TestSceneOsuScreenStack.cs @@ -42,7 +42,7 @@ namespace osu.Game.Tests.Visual AddAssert("Parallax is off", () => stack.ParallaxAmount == 0); } - private class TestScreen : ScreenWithBeatmapBackground + public class TestScreen : ScreenWithBeatmapBackground { private readonly string screenText; diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index a52edb70db..8f7ad2022d 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -262,8 +262,10 @@ namespace osu.Game.Screens.Select protected virtual void ApplyFilterToCarousel(FilterCriteria criteria) { - if (this.IsCurrentScreen()) - Carousel.Filter(criteria); + // if not the current screen, we want to get carousel in a good presentation state before displaying (resume or enter). + bool shouldDebounce = this.IsCurrentScreen(); + + Schedule(() => Carousel.Filter(criteria, shouldDebounce)); } private DependencyContainer dependencies; @@ -437,8 +439,6 @@ namespace osu.Game.Screens.Select { base.OnEntering(last); - Carousel.Filter(FilterControl.CreateCriteria(), false); - this.FadeInFromZero(250); FilterControl.Activate(); } diff --git a/osu.Game/Tests/Visual/ScreenTestScene.cs b/osu.Game/Tests/Visual/ScreenTestScene.cs index 23f45e0d0f..707aa61283 100644 --- a/osu.Game/Tests/Visual/ScreenTestScene.cs +++ b/osu.Game/Tests/Visual/ScreenTestScene.cs @@ -12,7 +12,7 @@ namespace osu.Game.Tests.Visual /// public abstract class ScreenTestScene : ManualInputManagerTestScene { - private readonly OsuScreenStack stack; + protected readonly OsuScreenStack Stack; private readonly Container content; @@ -22,16 +22,16 @@ namespace osu.Game.Tests.Visual { base.Content.AddRange(new Drawable[] { - stack = new OsuScreenStack { RelativeSizeAxes = Axes.Both }, + Stack = new OsuScreenStack { RelativeSizeAxes = Axes.Both }, content = new Container { RelativeSizeAxes = Axes.Both } }); } protected void LoadScreen(OsuScreen screen) { - if (stack.CurrentScreen != null) - stack.Exit(); - stack.Push(screen); + if (Stack.CurrentScreen != null) + Stack.Exit(); + Stack.Push(screen); } } } From 1db218f9082749578b8af301d7a329c677b8dfe1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 10 Dec 2019 03:29:25 +0900 Subject: [PATCH 83/89] Don't show count when deleting only one control point Reads better. --- .../Blueprints/Sliders/Components/PathControlPointVisualiser.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs index cdca48490e..629604357d 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs @@ -154,7 +154,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components return new MenuItem[] { - new OsuMenuItem($"Delete {"control point".ToQuantity(selectedPoints)}", MenuItemType.Destructive, () => deleteSelected()) + new OsuMenuItem($"Delete {"control point".ToQuantity(selectedPoints, selectedPoints > 1 ? ShowQuantityAs.Numeric : ShowQuantityAs.None)}", MenuItemType.Destructive, () => deleteSelected()) }; } } From ab0f2e7c6a1c1692a115ca54512344a162ec2230 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 10 Dec 2019 13:12:54 +0900 Subject: [PATCH 84/89] Apply suggested refactorings --- .../Rulesets/Objects/Legacy/ConvertHitObjectParser.cs | 9 ++++----- osu.Game/Rulesets/Objects/PathControlPoint.cs | 7 +++---- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs index 1ac7284772..b5b1e26486 100644 --- a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs @@ -257,14 +257,13 @@ namespace osu.Game.Rulesets.Objects.Legacy { if (type == PathType.PerfectCurve) { - if (vertices.Length == 3) + if (vertices.Length != 3) + type = PathType.Bezier; + else if (isLinear(vertices)) { // osu-stable special-cased colinear perfect curves to a linear path - if (isLinear(vertices)) - type = PathType.Linear; + type = PathType.Linear; } - else - type = PathType.Bezier; } var points = new List(vertices.Length) diff --git a/osu.Game/Rulesets/Objects/PathControlPoint.cs b/osu.Game/Rulesets/Objects/PathControlPoint.cs index 5737d3f618..0336f94313 100644 --- a/osu.Game/Rulesets/Objects/PathControlPoint.cs +++ b/osu.Game/Rulesets/Objects/PathControlPoint.cs @@ -30,8 +30,9 @@ namespace osu.Game.Rulesets.Objects /// Creates a new . /// public PathControlPoint() - : this(Vector2.Zero, null) { + Position.ValueChanged += _ => Changed?.Invoke(); + Type.ValueChanged += _ => Changed?.Invoke(); } /// @@ -40,12 +41,10 @@ namespace osu.Game.Rulesets.Objects /// The initial position. /// The initial type. public PathControlPoint(Vector2 position, PathType? type = null) + : this() { Position.Value = position; Type.Value = type; - - Position.ValueChanged += _ => Changed?.Invoke(); - Type.ValueChanged += _ => Changed?.Invoke(); } public bool Equals(PathControlPoint other) => Position.Value == other?.Position.Value && Type.Value == other.Type.Value; From 609c51130964ccb85e00160be1556ec4aa7bec61 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 10 Dec 2019 16:43:58 +0900 Subject: [PATCH 85/89] Update framework --- osu.Android.props | 2 +- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index 301c615ce4..914d352070 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -54,6 +54,6 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index ef16738908..473ce82443 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -23,7 +23,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index 5090190f28..d03005012a 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -74,7 +74,7 @@ - + @@ -82,7 +82,7 @@ - + From f7f4a57c5f6b1b3d64bec8c22461e261c05bae97 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 9 Dec 2019 19:41:18 +0900 Subject: [PATCH 86/89] Update bindable types in line with framework --- osu.Game/Rulesets/Mods/ModTimeAdjust.cs | 2 +- osu.Game/Rulesets/UI/DrawableRuleset.cs | 16 +++++++++++----- .../Tests/Visual/RateAdjustedBeatmapTestScene.cs | 2 +- 3 files changed, 13 insertions(+), 7 deletions(-) diff --git a/osu.Game/Rulesets/Mods/ModTimeAdjust.cs b/osu.Game/Rulesets/Mods/ModTimeAdjust.cs index f137a75ed8..7d0cc2a7c3 100644 --- a/osu.Game/Rulesets/Mods/ModTimeAdjust.cs +++ b/osu.Game/Rulesets/Mods/ModTimeAdjust.cs @@ -14,7 +14,7 @@ namespace osu.Game.Rulesets.Mods public virtual void ApplyToTrack(Track track) { - track.TempoAdjust *= RateAdjust; + track.Tempo.Value *= RateAdjust; } } } diff --git a/osu.Game/Rulesets/UI/DrawableRuleset.cs b/osu.Game/Rulesets/UI/DrawableRuleset.cs index 96275c1274..a856974292 100644 --- a/osu.Game/Rulesets/UI/DrawableRuleset.cs +++ b/osu.Game/Rulesets/UI/DrawableRuleset.cs @@ -511,15 +511,19 @@ namespace osu.Game.Rulesets.UI public IEnumerable GetAvailableResources() => throw new NotImplementedException(); - public void AddAdjustment(AdjustableProperty type, BindableDouble adjustBindable) => throw new NotImplementedException(); + public void AddAdjustment(AdjustableProperty type, BindableNumber adjustBindable) => throw new NotImplementedException(); - public void RemoveAdjustment(AdjustableProperty type, BindableDouble adjustBindable) => throw new NotImplementedException(); + public void RemoveAdjustment(AdjustableProperty type, BindableNumber adjustBindable) => throw new NotImplementedException(); - public BindableDouble Volume => throw new NotImplementedException(); + public BindableNumber Volume => throw new NotImplementedException(); - public BindableDouble Balance => throw new NotImplementedException(); + public BindableNumber Balance => throw new NotImplementedException(); - public BindableDouble Frequency => throw new NotImplementedException(); + public BindableNumber Frequency => throw new NotImplementedException(); + + public BindableNumber Tempo => throw new NotImplementedException(); + + public IBindable GetAggregate(AdjustableProperty type) => throw new NotImplementedException(); public IBindable AggregateVolume => throw new NotImplementedException(); @@ -527,6 +531,8 @@ namespace osu.Game.Rulesets.UI public IBindable AggregateFrequency => throw new NotImplementedException(); + public IBindable AggregateTempo => throw new NotImplementedException(); + public int PlaybackConcurrency { get => throw new NotImplementedException(); diff --git a/osu.Game/Tests/Visual/RateAdjustedBeatmapTestScene.cs b/osu.Game/Tests/Visual/RateAdjustedBeatmapTestScene.cs index 921a1d9789..ad24ffc7b8 100644 --- a/osu.Game/Tests/Visual/RateAdjustedBeatmapTestScene.cs +++ b/osu.Game/Tests/Visual/RateAdjustedBeatmapTestScene.cs @@ -13,7 +13,7 @@ namespace osu.Game.Tests.Visual base.Update(); // note that this will override any mod rate application - Beatmap.Value.Track.TempoAdjust = Clock.Rate; + Beatmap.Value.Track.Tempo.Value = Clock.Rate; } } } From 65f2d1f8757f809eb5f33b639abc5e7b338b6060 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 10 Dec 2019 17:49:42 +0900 Subject: [PATCH 87/89] Update framework --- osu.Android.props | 2 +- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index 914d352070..3cd4dc48bf 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -54,6 +54,6 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 473ce82443..530d62f583 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -23,7 +23,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index d03005012a..fb753b8c6f 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -74,7 +74,7 @@ - + @@ -82,7 +82,7 @@ - + From 55c938e5daa07a0a471d313645c172d5372ba73c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 10 Dec 2019 18:08:11 +0900 Subject: [PATCH 88/89] Fix bindable usage --- osu.Game/Online/DownloadTrackingComposite.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Online/DownloadTrackingComposite.cs b/osu.Game/Online/DownloadTrackingComposite.cs index dcec17788a..9a0e112727 100644 --- a/osu.Game/Online/DownloadTrackingComposite.cs +++ b/osu.Game/Online/DownloadTrackingComposite.cs @@ -26,7 +26,7 @@ namespace osu.Game.Online /// protected readonly Bindable State = new Bindable(); - protected readonly Bindable Progress = new Bindable(); + protected readonly BindableNumber Progress = new BindableNumber { MinValue = 0, MaxValue = 1 }; protected DownloadTrackingComposite(TModel model = null) { From f593caf0eaa4d8750d314d16f17278c7fdc178d8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 10 Dec 2019 18:08:51 +0900 Subject: [PATCH 89/89] Remove unused class --- .../Blueprints/Sliders/SliderPlacementBlueprint.cs | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs index c004b6db28..9b820261ab 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs @@ -1,7 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System.Collections.Generic; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Input; @@ -191,15 +190,5 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders Initial, Body, } - - private class Segment - { - public readonly List ControlPoints = new List(); - - public Segment(Vector2 offset) - { - ControlPoints.Add(offset); - } - } } }