From 06774309756a1b6ee4eec0cc70bf2f2e3faa900f Mon Sep 17 00:00:00 2001 From: OliBomby Date: Sat, 21 Sep 2024 20:34:29 +0200 Subject: [PATCH 01/75] compute lower bound in clamp scale --- .../Edit/OsuSelectionScaleHandler.cs | 25 +++++++++++++------ .../Edit/PreciseScalePopover.cs | 10 ++++++++ 2 files changed, 27 insertions(+), 8 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs b/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs index fc85865dd2..fa2aa3e39a 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs @@ -241,33 +241,42 @@ namespace osu.Game.Rulesets.Osu.Edit foreach (var point in points) { - scale = clampToBound(scale, point, Vector2.Zero); - scale = clampToBound(scale, point, OsuPlayfield.BASE_SIZE); + scale = clampToBound(scale, point, Vector2.Zero, OsuPlayfield.BASE_SIZE); } return Vector2.ComponentMax(scale, new Vector2(Precision.FLOAT_EPSILON)); - float minPositiveComponent(Vector2 v) => MathF.Min(v.X < 0 ? float.PositiveInfinity : v.X, v.Y < 0 ? float.PositiveInfinity : v.Y); + float minComponent(Vector2 v) => MathF.Min(v.X, v.Y); + float maxComponent(Vector2 v) => MathF.Max(v.X, v.Y); - Vector2 clampToBound(Vector2 s, Vector2 p, Vector2 bound) + Vector2 clampToBound(Vector2 s, Vector2 p, Vector2 lowerBound, Vector2 upperBound) { p -= actualOrigin; - bound -= actualOrigin; + lowerBound -= actualOrigin; + upperBound -= actualOrigin; var a = new Vector2(cos * cos * p.X - sin * cos * p.Y, -sin * cos * p.X + sin * sin * p.Y); var b = new Vector2(sin * sin * p.X + sin * cos * p.Y, sin * cos * p.X + cos * cos * p.Y); switch (adjustAxis) { case Axes.X: - s.X = MathF.Min(scale.X, minPositiveComponent(Vector2.Divide(bound - b, a))); + var lowerBounds = Vector2.Divide(lowerBound - b, a); + var upperBounds = Vector2.Divide(upperBound - b, a); + if (a.X < 0) + (lowerBounds, upperBounds) = (upperBounds, lowerBounds); + s.X = MathHelper.Clamp(s.X, maxComponent(lowerBounds), minComponent(upperBounds)); break; case Axes.Y: - s.Y = MathF.Min(scale.Y, minPositiveComponent(Vector2.Divide(bound - a, b))); + var lowerBoundsY = Vector2.Divide(lowerBound - a, b); + var upperBoundsY = Vector2.Divide(upperBound - a, b); + if (b.Y < 0) + (lowerBoundsY, upperBoundsY) = (upperBoundsY, lowerBoundsY); + s.Y = MathHelper.Clamp(s.Y, maxComponent(lowerBoundsY), minComponent(upperBoundsY)); break; case Axes.Both: - s = Vector2.ComponentMin(s, s * minPositiveComponent(Vector2.Divide(bound, a * s.X + b * s.Y))); + // s = Vector2.ComponentMin(s, s * minPositiveComponent(Vector2.Divide(bound, a * s.X + b * s.Y))); break; } diff --git a/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs b/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs index 33b0c14185..cea0864052 100644 --- a/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs +++ b/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs @@ -189,6 +189,16 @@ namespace osu.Game.Rulesets.Osu.Edit scale.Y = max_scale; scaleInputBindable.MaxValue = MathF.Max(1, MathF.Min(scale.X, scale.Y)); + + const float min_scale = -10; + scale = scaleHandler.ClampScaleToPlayfieldBounds(new Vector2(min_scale), getOriginPosition(scaleInfo.Value), getAdjustAxis(scaleInfo.Value), getRotation(scaleInfo.Value)); + + if (!scaleInfo.Value.XAxis) + scale.X = max_scale; + if (!scaleInfo.Value.YAxis) + scale.Y = max_scale; + + scaleInputBindable.MinValue = MathF.Min(-1, MathF.Max(scale.X, scale.Y)); } private void setOrigin(ScaleOrigin origin) From 08e1698bb6a7f27de3f0b737b1998e2108de3bdb Mon Sep 17 00:00:00 2001 From: OliBomby Date: Sat, 21 Sep 2024 22:04:11 +0200 Subject: [PATCH 02/75] fix scale clamp computation and code cleanup --- .../Edit/OsuSelectionScaleHandler.cs | 58 ++++++++++++------- .../Edit/PreciseScalePopover.cs | 9 +-- 2 files changed, 43 insertions(+), 24 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs b/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs index fa2aa3e39a..77fa64b0b1 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs @@ -240,48 +240,66 @@ namespace osu.Game.Rulesets.Osu.Edit points = originalConvexHull!; foreach (var point in points) - { scale = clampToBound(scale, point, Vector2.Zero, OsuPlayfield.BASE_SIZE); - } - return Vector2.ComponentMax(scale, new Vector2(Precision.FLOAT_EPSILON)); + return scale; - float minComponent(Vector2 v) => MathF.Min(v.X, v.Y); - float maxComponent(Vector2 v) => MathF.Max(v.X, v.Y); - - Vector2 clampToBound(Vector2 s, Vector2 p, Vector2 lowerBound, Vector2 upperBound) + Vector2 clampToBound(Vector2 s, Vector2 p, Vector2 lowerBounds, Vector2 upperBounds) { p -= actualOrigin; - lowerBound -= actualOrigin; - upperBound -= actualOrigin; + lowerBounds -= actualOrigin; + upperBounds -= actualOrigin; + // a.X is the rotated X component of p with respect to the X bounds + // a.Y is the rotated X component of p with respect to the Y bounds + // b.X is the rotated Y component of p with respect to the X bounds + // b.Y is the rotated Y component of p with respect to the Y bounds var a = new Vector2(cos * cos * p.X - sin * cos * p.Y, -sin * cos * p.X + sin * sin * p.Y); var b = new Vector2(sin * sin * p.X + sin * cos * p.Y, sin * cos * p.X + cos * cos * p.Y); + float sLowerBound, sUpperBound; + switch (adjustAxis) { case Axes.X: - var lowerBounds = Vector2.Divide(lowerBound - b, a); - var upperBounds = Vector2.Divide(upperBound - b, a); - if (a.X < 0) - (lowerBounds, upperBounds) = (upperBounds, lowerBounds); - s.X = MathHelper.Clamp(s.X, maxComponent(lowerBounds), minComponent(upperBounds)); + (sLowerBound, sUpperBound) = computeBounds(lowerBounds - b, upperBounds - b, a); + s.X = MathHelper.Clamp(s.X, sLowerBound, sUpperBound); break; case Axes.Y: - var lowerBoundsY = Vector2.Divide(lowerBound - a, b); - var upperBoundsY = Vector2.Divide(upperBound - a, b); - if (b.Y < 0) - (lowerBoundsY, upperBoundsY) = (upperBoundsY, lowerBoundsY); - s.Y = MathHelper.Clamp(s.Y, maxComponent(lowerBoundsY), minComponent(upperBoundsY)); + (sLowerBound, sUpperBound) = computeBounds(lowerBounds - a, upperBounds - a, b); + s.Y = MathHelper.Clamp(s.Y, sLowerBound, sUpperBound); break; case Axes.Both: - // s = Vector2.ComponentMin(s, s * minPositiveComponent(Vector2.Divide(bound, a * s.X + b * s.Y))); + // Here we compute the bounds for the magnitude multiplier of the scale vector + // Therefore the ratio s.X / s.Y will be maintained + (sLowerBound, sUpperBound) = computeBounds(lowerBounds, upperBounds, a * s.X + b * s.Y); + s.X = s.X < 0 + ? MathHelper.Clamp(s.X, s.X * sUpperBound, s.X * sLowerBound) + : MathHelper.Clamp(s.X, s.X * sLowerBound, s.X * sUpperBound); + s.Y = s.Y < 0 + ? MathHelper.Clamp(s.Y, s.Y * sUpperBound, s.Y * sLowerBound) + : MathHelper.Clamp(s.Y, s.Y * sLowerBound, s.Y * sUpperBound); break; } return s; } + + (float, float) computeBounds(Vector2 lowerBounds, Vector2 upperBounds, Vector2 p) + { + var sLowerBounds = Vector2.Divide(lowerBounds, p); + var sUpperBounds = Vector2.Divide(upperBounds, p); + if (p.X < 0) + (sLowerBounds.X, sUpperBounds.X) = (sUpperBounds.X, sLowerBounds.X); + if (p.Y < 0) + (sLowerBounds.Y, sUpperBounds.Y) = (sUpperBounds.Y, sLowerBounds.Y); + if (Precision.AlmostEquals(p.X, 0)) + (sLowerBounds.X, sUpperBounds.X) = (float.NegativeInfinity, float.PositiveInfinity); + if (Precision.AlmostEquals(p.Y, 0)) + (sLowerBounds.Y, sUpperBounds.Y) = (float.NegativeInfinity, float.PositiveInfinity); + return (MathF.Max(sLowerBounds.X, sLowerBounds.Y), MathF.Min(sUpperBounds.X, sUpperBounds.Y)); + } } private void moveSelectionInBounds() diff --git a/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs b/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs index cea0864052..2c17229e02 100644 --- a/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs +++ b/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs @@ -180,7 +180,9 @@ namespace osu.Game.Rulesets.Osu.Edit if (!scaleHandler.OriginalSurroundingQuad.HasValue) return; + const float min_scale = -10; const float max_scale = 10; + var scale = scaleHandler.ClampScaleToPlayfieldBounds(new Vector2(max_scale), getOriginPosition(scaleInfo.Value), getAdjustAxis(scaleInfo.Value), getRotation(scaleInfo.Value)); if (!scaleInfo.Value.XAxis) @@ -190,15 +192,14 @@ namespace osu.Game.Rulesets.Osu.Edit scaleInputBindable.MaxValue = MathF.Max(1, MathF.Min(scale.X, scale.Y)); - const float min_scale = -10; scale = scaleHandler.ClampScaleToPlayfieldBounds(new Vector2(min_scale), getOriginPosition(scaleInfo.Value), getAdjustAxis(scaleInfo.Value), getRotation(scaleInfo.Value)); if (!scaleInfo.Value.XAxis) - scale.X = max_scale; + scale.X = min_scale; if (!scaleInfo.Value.YAxis) - scale.Y = max_scale; + scale.Y = min_scale; - scaleInputBindable.MinValue = MathF.Min(-1, MathF.Max(scale.X, scale.Y)); + scaleInputBindable.MinValue = MathF.Min(1, MathF.Max(scale.X, scale.Y)); } private void setOrigin(ScaleOrigin origin) From 0695d51968310671da063a0fa4ddf08d808f8a6b Mon Sep 17 00:00:00 2001 From: OliBomby Date: Tue, 1 Oct 2024 16:48:55 +0200 Subject: [PATCH 03/75] Set minimum scale to 0.5 --- osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs b/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs index 2c17229e02..cf4473bd41 100644 --- a/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs +++ b/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs @@ -180,7 +180,7 @@ namespace osu.Game.Rulesets.Osu.Edit if (!scaleHandler.OriginalSurroundingQuad.HasValue) return; - const float min_scale = -10; + const float min_scale = 0.5f; const float max_scale = 10; var scale = scaleHandler.ClampScaleToPlayfieldBounds(new Vector2(max_scale), getOriginPosition(scaleInfo.Value), getAdjustAxis(scaleInfo.Value), getRotation(scaleInfo.Value)); From 19d8be4890fdbd15ec00030397db2be2b45a4e21 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Thu, 3 Oct 2024 11:49:45 +0200 Subject: [PATCH 04/75] Add more comments --- osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs b/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs index 77fa64b0b1..8e94112866 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs @@ -244,6 +244,7 @@ namespace osu.Game.Rulesets.Osu.Edit return scale; + // Clamps the scale vector s such that the point p scaled by s is within the rectangle defined by lowerBounds and upperBounds Vector2 clampToBound(Vector2 s, Vector2 p, Vector2 lowerBounds, Vector2 upperBounds) { p -= actualOrigin; @@ -286,18 +287,25 @@ namespace osu.Game.Rulesets.Osu.Edit return s; } + // Computes the bounds for the magnitude of the scaled point p with respect to the bounds lowerBounds and upperBounds (float, float) computeBounds(Vector2 lowerBounds, Vector2 upperBounds, Vector2 p) { var sLowerBounds = Vector2.Divide(lowerBounds, p); var sUpperBounds = Vector2.Divide(upperBounds, p); + + // If the point is negative, then the bounds are flipped if (p.X < 0) (sLowerBounds.X, sUpperBounds.X) = (sUpperBounds.X, sLowerBounds.X); if (p.Y < 0) (sLowerBounds.Y, sUpperBounds.Y) = (sUpperBounds.Y, sLowerBounds.Y); + + // If the point is at zero, then any scale will have no effect on the point so the bounds are infinite + // The float division would already give us infinity for the bounds, but the sign is not consistent so we have to manually set it if (Precision.AlmostEquals(p.X, 0)) (sLowerBounds.X, sUpperBounds.X) = (float.NegativeInfinity, float.PositiveInfinity); if (Precision.AlmostEquals(p.Y, 0)) (sLowerBounds.Y, sUpperBounds.Y) = (float.NegativeInfinity, float.PositiveInfinity); + return (MathF.Max(sLowerBounds.X, sLowerBounds.Y), MathF.Min(sUpperBounds.X, sUpperBounds.Y)); } } From f8e43fd8d36ad6501b1617013824d1ee87a33b89 Mon Sep 17 00:00:00 2001 From: WitherFlower <52672543+WitherFlower@users.noreply.github.com> Date: Tue, 8 Oct 2024 13:46:21 +0200 Subject: [PATCH 05/75] Add tests for ranked and submitted date filtering --- .../Filtering/FilterQueryParserTest.cs | 82 +++++++++++++++++++ 1 file changed, 82 insertions(+) diff --git a/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs b/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs index 9ecfa72947..852b1e1441 100644 --- a/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs +++ b/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs @@ -627,6 +627,88 @@ namespace osu.Game.Tests.NonVisual.Filtering Assert.AreEqual(DateTimeOffset.MinValue.AddMilliseconds(1), filterCriteria.LastPlayed.Min); } + private static DateTimeOffset dateTimeOffsetFromDateOnly(int year, int month, int day) => + new DateTimeOffset(year, month, day, 0, 0, 0, TimeSpan.Zero); + + private static readonly object[] ranked_date_valid_test_cases = + { + new object[] { "ranked<2012", dateTimeOffsetFromDateOnly(2012, 1, 1), (FilterCriteria x) => x.DateRanked.Max }, + new object[] { "ranked<2012-03", dateTimeOffsetFromDateOnly(2012, 3, 1), (FilterCriteria x) => x.DateRanked.Max }, + new object[] { "ranked<2012-03-05", dateTimeOffsetFromDateOnly(2012, 3, 5), (FilterCriteria x) => x.DateRanked.Max }, + new object[] { "ranked<2012-3-5", dateTimeOffsetFromDateOnly(2012, 3, 5), (FilterCriteria x) => x.DateRanked.Max }, + + new object[] { "ranked<=2012", dateTimeOffsetFromDateOnly(2013, 1, 1), (FilterCriteria x) => x.DateRanked.Max }, + new object[] { "ranked<=2012-03", dateTimeOffsetFromDateOnly(2012, 4, 1), (FilterCriteria x) => x.DateRanked.Max }, + new object[] { "ranked<=2012-03-05", dateTimeOffsetFromDateOnly(2012, 3, 6), (FilterCriteria x) => x.DateRanked.Max }, + new object[] { "ranked<=2012-3-5", dateTimeOffsetFromDateOnly(2012, 3, 6), (FilterCriteria x) => x.DateRanked.Max }, + + new object[] { "ranked>2012", dateTimeOffsetFromDateOnly(2013, 1, 1), (FilterCriteria x) => x.DateRanked.Min }, + new object[] { "ranked>2012-03", dateTimeOffsetFromDateOnly(2012, 4, 1), (FilterCriteria x) => x.DateRanked.Min }, + new object[] { "ranked>2012-03-05", dateTimeOffsetFromDateOnly(2012, 3, 6), (FilterCriteria x) => x.DateRanked.Min }, + new object[] { "ranked>2012-3-5", dateTimeOffsetFromDateOnly(2012, 3, 6), (FilterCriteria x) => x.DateRanked.Min }, + + new object[] { "ranked>=2012", dateTimeOffsetFromDateOnly(2012, 1, 1), (FilterCriteria x) => x.DateRanked.Min }, + new object[] { "ranked>=2012-03", dateTimeOffsetFromDateOnly(2012, 3, 1), (FilterCriteria x) => x.DateRanked.Min }, + new object[] { "ranked>=2012-03-05", dateTimeOffsetFromDateOnly(2012, 3, 5), (FilterCriteria x) => x.DateRanked.Min }, + new object[] { "ranked>=2012-3-5", dateTimeOffsetFromDateOnly(2012, 3, 5), (FilterCriteria x) => x.DateRanked.Min }, + + new object[] { "ranked=2012", dateTimeOffsetFromDateOnly(2012, 1, 1), (FilterCriteria x) => x.DateRanked.Min }, + new object[] { "ranked=2012", dateTimeOffsetFromDateOnly(2012, 1, 1), (FilterCriteria x) => x.DateRanked.Min }, + new object[] { "ranked=2012-03", dateTimeOffsetFromDateOnly(2012, 3, 1), (FilterCriteria x) => x.DateRanked.Min }, + new object[] { "ranked=2012-03", dateTimeOffsetFromDateOnly(2012, 4, 1), (FilterCriteria x) => x.DateRanked.Max }, + new object[] { "ranked=2012-03-05", dateTimeOffsetFromDateOnly(2012, 3, 5), (FilterCriteria x) => x.DateRanked.Min }, + new object[] { "ranked=2012-03-05", dateTimeOffsetFromDateOnly(2012, 3, 6), (FilterCriteria x) => x.DateRanked.Max }, + }; + + [Test] + [TestCaseSource(nameof(ranked_date_valid_test_cases))] + public void TestValidRankedDateQueries(string query, DateTimeOffset expected, Func f) + { + var filterCriteria = new FilterCriteria(); + FilterQueryParser.ApplyQueries(filterCriteria, query); + Assert.AreEqual(true, filterCriteria.DateRanked.HasFilter); + Assert.AreEqual(expected, f(filterCriteria)); + } + + private static readonly object[] ranked_date_invalid_test_cases = + { + new object[] { "ranked<0" }, + new object[] { "ranked=99999" }, + new object[] { "ranked>=2012-03-05-04" }, + new object[] { "ranked>2007.5" }, + }; + + [Test] + [TestCaseSource(nameof(ranked_date_invalid_test_cases))] + public void TestInvalidRankedDateQueries(string query) + { + var filterCriteria = new FilterCriteria(); + FilterQueryParser.ApplyQueries(filterCriteria, query); + Assert.AreEqual(false, filterCriteria.DateRanked.HasFilter); + } + + private static readonly object[] submitted_date_test_cases = + { + new object[] { "submitted<2012", true }, + new object[] { "submitted<2012-03", true }, + new object[] { "submitted<2012-03-05", true }, + new object[] { "submitted<2012-3-5", true }, + + new object[] { "submitted<0", false }, + new object[] { "submitted=99999", false }, + new object[] { "submitted>=2012-03-05-04", false }, + new object[] { "submitted>2007.5", false }, + }; + + [Test] + [TestCaseSource(nameof(submitted_date_test_cases))] + public void TestInvalidRankedDateQueries(string query, bool expected) + { + var filterCriteria = new FilterCriteria(); + FilterQueryParser.ApplyQueries(filterCriteria, query); + Assert.AreEqual(expected, filterCriteria.DateSubmitted.HasFilter); + } + private static readonly object[] played_query_tests = { new object[] { "0", DateTimeOffset.MinValue, true }, From 2369e98cfc34ecb9c75ac66e89403eed10807e09 Mon Sep 17 00:00:00 2001 From: WitherFlower <52672543+WitherFlower@users.noreply.github.com> Date: Tue, 8 Oct 2024 13:46:58 +0200 Subject: [PATCH 06/75] Implement ranked and submitted date filtering --- .../Select/Carousel/CarouselBeatmap.cs | 2 + osu.Game/Screens/Select/FilterCriteria.cs | 2 + osu.Game/Screens/Select/FilterQueryParser.cs | 185 ++++++++++++++++++ 3 files changed, 189 insertions(+) diff --git a/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs index 8f38ae710c..aa064f97c9 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs @@ -66,6 +66,8 @@ namespace osu.Game.Screens.Select.Carousel match &= !criteria.OverallDifficulty.HasFilter || criteria.OverallDifficulty.IsInRange(BeatmapInfo.Difficulty.OverallDifficulty); match &= !criteria.Length.HasFilter || criteria.Length.IsInRange(BeatmapInfo.Length); match &= !criteria.LastPlayed.HasFilter || criteria.LastPlayed.IsInRange(BeatmapInfo.LastPlayed ?? DateTimeOffset.MinValue); + match &= !criteria.DateRanked.HasFilter || criteria.DateRanked.IsInRange(BeatmapInfo.BeatmapSet?.DateRanked ?? DateTimeOffset.MinValue); + match &= !criteria.DateSubmitted.HasFilter || criteria.DateSubmitted.IsInRange(BeatmapInfo.BeatmapSet?.DateSubmitted ?? DateTimeOffset.MinValue); match &= !criteria.BPM.HasFilter || criteria.BPM.IsInRange(BeatmapInfo.BPM); match &= !criteria.BeatDivisor.HasFilter || criteria.BeatDivisor.IsInRange(BeatmapInfo.BeatDivisor); diff --git a/osu.Game/Screens/Select/FilterCriteria.cs b/osu.Game/Screens/Select/FilterCriteria.cs index 190efd0fb0..77d7ff0e9f 100644 --- a/osu.Game/Screens/Select/FilterCriteria.cs +++ b/osu.Game/Screens/Select/FilterCriteria.cs @@ -37,6 +37,8 @@ namespace osu.Game.Screens.Select public OptionalRange BeatDivisor; public OptionalSet OnlineStatus = new OptionalSet(); public OptionalRange LastPlayed; + public OptionalRange DateRanked; + public OptionalRange DateSubmitted; public OptionalTextFilter Creator; public OptionalTextFilter Artist; public OptionalTextFilter Title; diff --git a/osu.Game/Screens/Select/FilterQueryParser.cs b/osu.Game/Screens/Select/FilterQueryParser.cs index 6c9a95a250..17fa6598c5 100644 --- a/osu.Game/Screens/Select/FilterQueryParser.cs +++ b/osu.Game/Screens/Select/FilterQueryParser.cs @@ -65,6 +65,12 @@ namespace osu.Game.Screens.Select case "lastplayed": return tryUpdateDateAgoRange(ref criteria.LastPlayed, op, value); + case "ranked": + return tryUpdateRankedDateRange(ref criteria.DateRanked, op, value); + + case "submitted": + return tryUpdateRankedDateRange(ref criteria.DateSubmitted, op, value); + case "played": if (!tryParseBool(value, out bool played)) return false; @@ -592,5 +598,184 @@ namespace osu.Game.Screens.Select return tryUpdateCriteriaRange(ref dateRange, op, dateTimeOffset.Value); } + + /// + /// Helper function for building a UTC date from only the year, month and day. + /// UTC is used to keep consistent search results with osu!web. + /// + private static DateTimeOffset dateTimeOffsetFromDateOnly(int year, int month, int day) => + new DateTimeOffset(year, month, day, 0, 0, 0, TimeSpan.Zero); + + /// + /// Parses a string containing a ranked or submitted date filter. + /// Returns a boolean depending on whether parsing was successful or not. + /// Accepted dates are in the formats `yyyy`, `yyyy-mm` and `yyyy-mm-dd`. Leading zeros are accepted. + /// + /// The to store the parsed data into, if successful. + /// The operator of the filtering query + /// The string value to attempt parsing for. + private static bool tryUpdateRankedDateRange(ref FilterCriteria.OptionalRange dateRange, Operator op, string val) + { + GroupCollection? match = null; + + match ??= tryMatchRegex(val, @"^(?\d+)(-(?\d+)(-(?\d+))?)?$"); + + if (match == null) + return false; + + int? year = null; + int? month = null; + int? day = null; + + List keys = new List { @"year", @"month", @"day" }; + + foreach (string key in keys) + { + if (!match.TryGetValue(key, out var group) || !group.Success) + continue; + + if (group.Success) + { + if (!tryParseDoubleWithPoint(group.Value, out double value)) + return false; + + switch (key) + { + + case @"year": + year = (int)value; + break; + + case @"month": + month = (int)value; + break; + + case @"day": + day = (int)value; + break; + } + } + } + + if (year == null) + { + return false; + } + + try + { + DateTimeOffset dateTimeOffset; + + switch (op) + { + case Operator.Less: + if (month == null) + { + month = 1; + day = 1; + } + + if (day == null) + day = 1; + + dateTimeOffset = dateTimeOffsetFromDateOnly(year.Value, month.Value, day.Value); + return tryUpdateCriteriaRange(ref dateRange, op, dateTimeOffset); + + case Operator.LessOrEqual: + if (month == null) + { + month = 1; + day = 1; + dateTimeOffset = dateTimeOffsetFromDateOnly(year.Value, month.Value, day.Value).AddYears(1); + return tryUpdateCriteriaRange(ref dateRange, Operator.Less, dateTimeOffset); + } + if (day == null) + { + day = 1; + dateTimeOffset = dateTimeOffsetFromDateOnly(year.Value, month.Value, day.Value).AddMonths(1); + return tryUpdateCriteriaRange(ref dateRange, Operator.Less, dateTimeOffset); + } + + dateTimeOffset = dateTimeOffsetFromDateOnly(year.Value, month.Value, day.Value).AddDays(1); + return tryUpdateCriteriaRange(ref dateRange, Operator.Less, dateTimeOffset); + + case Operator.GreaterOrEqual: + if (month == null) + { + month = 1; + day = 1; + } + + if (day == null) + day = 1; + + dateTimeOffset = dateTimeOffsetFromDateOnly(year.Value, month.Value, day.Value); + return tryUpdateCriteriaRange(ref dateRange, op, dateTimeOffset); + + case Operator.Greater: + if (month == null) + { + month = 1; + day = 1; + dateTimeOffset = dateTimeOffsetFromDateOnly(year.Value, month.Value, day.Value).AddYears(1); + return tryUpdateCriteriaRange(ref dateRange, Operator.GreaterOrEqual, dateTimeOffset); + } + if (day == null) + { + day = 1; + dateTimeOffset = dateTimeOffsetFromDateOnly(year.Value, month.Value, day.Value).AddMonths(1); + return tryUpdateCriteriaRange(ref dateRange, Operator.GreaterOrEqual, dateTimeOffset); + } + + dateTimeOffset = dateTimeOffsetFromDateOnly(year.Value, month.Value, day.Value).AddDays(1); + return tryUpdateCriteriaRange(ref dateRange, Operator.GreaterOrEqual, dateTimeOffset); + + case Operator.Equal: + + DateTimeOffset minDateTimeOffset; + DateTimeOffset maxDateTimeOffset; + + if (month == null) + { + month = 1; + day = 1; + minDateTimeOffset = dateTimeOffsetFromDateOnly(year.Value, month.Value, day.Value); + maxDateTimeOffset = dateTimeOffsetFromDateOnly(year.Value, month.Value, day.Value).AddYears(1); + return ( + tryUpdateCriteriaRange(ref dateRange, Operator.GreaterOrEqual, minDateTimeOffset) + && + tryUpdateCriteriaRange(ref dateRange, Operator.Less, maxDateTimeOffset) + ); + } + + if (day == null) + { + day = 1; + minDateTimeOffset = dateTimeOffsetFromDateOnly(year.Value, month.Value, day.Value); + maxDateTimeOffset = dateTimeOffsetFromDateOnly(year.Value, month.Value, day.Value).AddMonths(1); + return ( + tryUpdateCriteriaRange(ref dateRange, Operator.GreaterOrEqual, minDateTimeOffset) + && + tryUpdateCriteriaRange(ref dateRange, Operator.Less, maxDateTimeOffset) + ); + } + + minDateTimeOffset = dateTimeOffsetFromDateOnly(year.Value, month.Value, day.Value); + maxDateTimeOffset = dateTimeOffsetFromDateOnly(year.Value, month.Value, day.Value).AddDays(1); + + return ( + tryUpdateCriteriaRange(ref dateRange, Operator.GreaterOrEqual, minDateTimeOffset) + && + tryUpdateCriteriaRange(ref dateRange, Operator.Less, maxDateTimeOffset) + ); + default: + return false; + } + } + catch (ArgumentOutOfRangeException) + { + return false; + } + } } } From b58576f31b05aeb18af742e6145c5a66f375ba20 Mon Sep 17 00:00:00 2001 From: WitherFlower <52672543+WitherFlower@users.noreply.github.com> Date: Tue, 8 Oct 2024 14:13:27 +0200 Subject: [PATCH 07/75] Add slash and dot as valid separators in dates. --- .../Filtering/FilterQueryParserTest.cs | 23 +++++++++---------- osu.Game/Screens/Select/FilterQueryParser.cs | 5 ++-- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs b/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs index 852b1e1441..ad3be17c15 100644 --- a/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs +++ b/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs @@ -633,23 +633,23 @@ namespace osu.Game.Tests.NonVisual.Filtering private static readonly object[] ranked_date_valid_test_cases = { new object[] { "ranked<2012", dateTimeOffsetFromDateOnly(2012, 1, 1), (FilterCriteria x) => x.DateRanked.Max }, - new object[] { "ranked<2012-03", dateTimeOffsetFromDateOnly(2012, 3, 1), (FilterCriteria x) => x.DateRanked.Max }, - new object[] { "ranked<2012-03-05", dateTimeOffsetFromDateOnly(2012, 3, 5), (FilterCriteria x) => x.DateRanked.Max }, + new object[] { "ranked<2012.03", dateTimeOffsetFromDateOnly(2012, 3, 1), (FilterCriteria x) => x.DateRanked.Max }, + new object[] { "ranked<2012/03/05", dateTimeOffsetFromDateOnly(2012, 3, 5), (FilterCriteria x) => x.DateRanked.Max }, new object[] { "ranked<2012-3-5", dateTimeOffsetFromDateOnly(2012, 3, 5), (FilterCriteria x) => x.DateRanked.Max }, new object[] { "ranked<=2012", dateTimeOffsetFromDateOnly(2013, 1, 1), (FilterCriteria x) => x.DateRanked.Max }, - new object[] { "ranked<=2012-03", dateTimeOffsetFromDateOnly(2012, 4, 1), (FilterCriteria x) => x.DateRanked.Max }, - new object[] { "ranked<=2012-03-05", dateTimeOffsetFromDateOnly(2012, 3, 6), (FilterCriteria x) => x.DateRanked.Max }, + new object[] { "ranked<=2012.03", dateTimeOffsetFromDateOnly(2012, 4, 1), (FilterCriteria x) => x.DateRanked.Max }, + new object[] { "ranked<=2012/03/05", dateTimeOffsetFromDateOnly(2012, 3, 6), (FilterCriteria x) => x.DateRanked.Max }, new object[] { "ranked<=2012-3-5", dateTimeOffsetFromDateOnly(2012, 3, 6), (FilterCriteria x) => x.DateRanked.Max }, new object[] { "ranked>2012", dateTimeOffsetFromDateOnly(2013, 1, 1), (FilterCriteria x) => x.DateRanked.Min }, - new object[] { "ranked>2012-03", dateTimeOffsetFromDateOnly(2012, 4, 1), (FilterCriteria x) => x.DateRanked.Min }, - new object[] { "ranked>2012-03-05", dateTimeOffsetFromDateOnly(2012, 3, 6), (FilterCriteria x) => x.DateRanked.Min }, + new object[] { "ranked>2012.03", dateTimeOffsetFromDateOnly(2012, 4, 1), (FilterCriteria x) => x.DateRanked.Min }, + new object[] { "ranked>2012/03/05", dateTimeOffsetFromDateOnly(2012, 3, 6), (FilterCriteria x) => x.DateRanked.Min }, new object[] { "ranked>2012-3-5", dateTimeOffsetFromDateOnly(2012, 3, 6), (FilterCriteria x) => x.DateRanked.Min }, new object[] { "ranked>=2012", dateTimeOffsetFromDateOnly(2012, 1, 1), (FilterCriteria x) => x.DateRanked.Min }, - new object[] { "ranked>=2012-03", dateTimeOffsetFromDateOnly(2012, 3, 1), (FilterCriteria x) => x.DateRanked.Min }, - new object[] { "ranked>=2012-03-05", dateTimeOffsetFromDateOnly(2012, 3, 5), (FilterCriteria x) => x.DateRanked.Min }, + new object[] { "ranked>=2012.03", dateTimeOffsetFromDateOnly(2012, 3, 1), (FilterCriteria x) => x.DateRanked.Min }, + new object[] { "ranked>=2012/03/05", dateTimeOffsetFromDateOnly(2012, 3, 5), (FilterCriteria x) => x.DateRanked.Min }, new object[] { "ranked>=2012-3-5", dateTimeOffsetFromDateOnly(2012, 3, 5), (FilterCriteria x) => x.DateRanked.Min }, new object[] { "ranked=2012", dateTimeOffsetFromDateOnly(2012, 1, 1), (FilterCriteria x) => x.DateRanked.Min }, @@ -675,7 +675,6 @@ namespace osu.Game.Tests.NonVisual.Filtering new object[] { "ranked<0" }, new object[] { "ranked=99999" }, new object[] { "ranked>=2012-03-05-04" }, - new object[] { "ranked>2007.5" }, }; [Test] @@ -690,14 +689,14 @@ namespace osu.Game.Tests.NonVisual.Filtering private static readonly object[] submitted_date_test_cases = { new object[] { "submitted<2012", true }, - new object[] { "submitted<2012-03", true }, - new object[] { "submitted<2012-03-05", true }, + new object[] { "submitted<2012.03", true }, + new object[] { "submitted<2012/03/05", true }, new object[] { "submitted<2012-3-5", true }, new object[] { "submitted<0", false }, new object[] { "submitted=99999", false }, new object[] { "submitted>=2012-03-05-04", false }, - new object[] { "submitted>2007.5", false }, + new object[] { "submitted>=2012/03.05-04", false }, }; [Test] diff --git a/osu.Game/Screens/Select/FilterQueryParser.cs b/osu.Game/Screens/Select/FilterQueryParser.cs index 17fa6598c5..d6f46c0fbd 100644 --- a/osu.Game/Screens/Select/FilterQueryParser.cs +++ b/osu.Game/Screens/Select/FilterQueryParser.cs @@ -609,7 +609,8 @@ namespace osu.Game.Screens.Select /// /// Parses a string containing a ranked or submitted date filter. /// Returns a boolean depending on whether parsing was successful or not. - /// Accepted dates are in the formats `yyyy`, `yyyy-mm` and `yyyy-mm-dd`. Leading zeros are accepted. + /// Accepted dates are in the formats `yyyy`, `yyyy-mm` and `yyyy-mm-dd`. + /// Leading zeros are accepted. Numbers can be separated by `-`, `/`, or `.` /// /// The to store the parsed data into, if successful. /// The operator of the filtering query @@ -618,7 +619,7 @@ namespace osu.Game.Screens.Select { GroupCollection? match = null; - match ??= tryMatchRegex(val, @"^(?\d+)(-(?\d+)(-(?\d+))?)?$"); + match ??= tryMatchRegex(val, @"^(?\d+)((-|/|\.)(?\d+)((-|/|\.)(?\d+))?)?$"); if (match == null) return false; From 6b532824b1b07605f45e314900186a410649ea9a Mon Sep 17 00:00:00 2001 From: WitherFlower <52672543+WitherFlower@users.noreply.github.com> Date: Wed, 9 Oct 2024 13:29:03 +0200 Subject: [PATCH 08/75] Fix code quality and formatting issues --- .../Filtering/FilterQueryParserTest.cs | 52 +++++++++---------- osu.Game/Screens/Select/FilterQueryParser.cs | 44 +++++----------- 2 files changed, 38 insertions(+), 58 deletions(-) diff --git a/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs b/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs index ad3be17c15..c8f063719d 100644 --- a/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs +++ b/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs @@ -632,32 +632,32 @@ namespace osu.Game.Tests.NonVisual.Filtering private static readonly object[] ranked_date_valid_test_cases = { - new object[] { "ranked<2012", dateTimeOffsetFromDateOnly(2012, 1, 1), (FilterCriteria x) => x.DateRanked.Max }, - new object[] { "ranked<2012.03", dateTimeOffsetFromDateOnly(2012, 3, 1), (FilterCriteria x) => x.DateRanked.Max }, - new object[] { "ranked<2012/03/05", dateTimeOffsetFromDateOnly(2012, 3, 5), (FilterCriteria x) => x.DateRanked.Max }, - new object[] { "ranked<2012-3-5", dateTimeOffsetFromDateOnly(2012, 3, 5), (FilterCriteria x) => x.DateRanked.Max }, + new object[] { "ranked<2012", dateTimeOffsetFromDateOnly(2012, 1, 1), (FilterCriteria x) => x.DateRanked.Max }, + new object[] { "ranked<2012.03", dateTimeOffsetFromDateOnly(2012, 3, 1), (FilterCriteria x) => x.DateRanked.Max }, + new object[] { "ranked<2012/03/05", dateTimeOffsetFromDateOnly(2012, 3, 5), (FilterCriteria x) => x.DateRanked.Max }, + new object[] { "ranked<2012-3-5", dateTimeOffsetFromDateOnly(2012, 3, 5), (FilterCriteria x) => x.DateRanked.Max }, - new object[] { "ranked<=2012", dateTimeOffsetFromDateOnly(2013, 1, 1), (FilterCriteria x) => x.DateRanked.Max }, - new object[] { "ranked<=2012.03", dateTimeOffsetFromDateOnly(2012, 4, 1), (FilterCriteria x) => x.DateRanked.Max }, + new object[] { "ranked<=2012", dateTimeOffsetFromDateOnly(2013, 1, 1), (FilterCriteria x) => x.DateRanked.Max }, + new object[] { "ranked<=2012.03", dateTimeOffsetFromDateOnly(2012, 4, 1), (FilterCriteria x) => x.DateRanked.Max }, new object[] { "ranked<=2012/03/05", dateTimeOffsetFromDateOnly(2012, 3, 6), (FilterCriteria x) => x.DateRanked.Max }, - new object[] { "ranked<=2012-3-5", dateTimeOffsetFromDateOnly(2012, 3, 6), (FilterCriteria x) => x.DateRanked.Max }, + new object[] { "ranked<=2012-3-5", dateTimeOffsetFromDateOnly(2012, 3, 6), (FilterCriteria x) => x.DateRanked.Max }, - new object[] { "ranked>2012", dateTimeOffsetFromDateOnly(2013, 1, 1), (FilterCriteria x) => x.DateRanked.Min }, - new object[] { "ranked>2012.03", dateTimeOffsetFromDateOnly(2012, 4, 1), (FilterCriteria x) => x.DateRanked.Min }, - new object[] { "ranked>2012/03/05", dateTimeOffsetFromDateOnly(2012, 3, 6), (FilterCriteria x) => x.DateRanked.Min }, - new object[] { "ranked>2012-3-5", dateTimeOffsetFromDateOnly(2012, 3, 6), (FilterCriteria x) => x.DateRanked.Min }, + new object[] { "ranked>2012", dateTimeOffsetFromDateOnly(2013, 1, 1), (FilterCriteria x) => x.DateRanked.Min }, + new object[] { "ranked>2012.03", dateTimeOffsetFromDateOnly(2012, 4, 1), (FilterCriteria x) => x.DateRanked.Min }, + new object[] { "ranked>2012/03/05", dateTimeOffsetFromDateOnly(2012, 3, 6), (FilterCriteria x) => x.DateRanked.Min }, + new object[] { "ranked>2012-3-5", dateTimeOffsetFromDateOnly(2012, 3, 6), (FilterCriteria x) => x.DateRanked.Min }, - new object[] { "ranked>=2012", dateTimeOffsetFromDateOnly(2012, 1, 1), (FilterCriteria x) => x.DateRanked.Min }, - new object[] { "ranked>=2012.03", dateTimeOffsetFromDateOnly(2012, 3, 1), (FilterCriteria x) => x.DateRanked.Min }, + new object[] { "ranked>=2012", dateTimeOffsetFromDateOnly(2012, 1, 1), (FilterCriteria x) => x.DateRanked.Min }, + new object[] { "ranked>=2012.03", dateTimeOffsetFromDateOnly(2012, 3, 1), (FilterCriteria x) => x.DateRanked.Min }, new object[] { "ranked>=2012/03/05", dateTimeOffsetFromDateOnly(2012, 3, 5), (FilterCriteria x) => x.DateRanked.Min }, - new object[] { "ranked>=2012-3-5", dateTimeOffsetFromDateOnly(2012, 3, 5), (FilterCriteria x) => x.DateRanked.Min }, + new object[] { "ranked>=2012-3-5", dateTimeOffsetFromDateOnly(2012, 3, 5), (FilterCriteria x) => x.DateRanked.Min }, - new object[] { "ranked=2012", dateTimeOffsetFromDateOnly(2012, 1, 1), (FilterCriteria x) => x.DateRanked.Min }, - new object[] { "ranked=2012", dateTimeOffsetFromDateOnly(2012, 1, 1), (FilterCriteria x) => x.DateRanked.Min }, - new object[] { "ranked=2012-03", dateTimeOffsetFromDateOnly(2012, 3, 1), (FilterCriteria x) => x.DateRanked.Min }, - new object[] { "ranked=2012-03", dateTimeOffsetFromDateOnly(2012, 4, 1), (FilterCriteria x) => x.DateRanked.Max }, - new object[] { "ranked=2012-03-05", dateTimeOffsetFromDateOnly(2012, 3, 5), (FilterCriteria x) => x.DateRanked.Min }, - new object[] { "ranked=2012-03-05", dateTimeOffsetFromDateOnly(2012, 3, 6), (FilterCriteria x) => x.DateRanked.Max }, + new object[] { "ranked=2012", dateTimeOffsetFromDateOnly(2012, 1, 1), (FilterCriteria x) => x.DateRanked.Min }, + new object[] { "ranked=2012", dateTimeOffsetFromDateOnly(2012, 1, 1), (FilterCriteria x) => x.DateRanked.Min }, + new object[] { "ranked=2012-03", dateTimeOffsetFromDateOnly(2012, 3, 1), (FilterCriteria x) => x.DateRanked.Min }, + new object[] { "ranked=2012-03", dateTimeOffsetFromDateOnly(2012, 4, 1), (FilterCriteria x) => x.DateRanked.Max }, + new object[] { "ranked=2012-03-05", dateTimeOffsetFromDateOnly(2012, 3, 5), (FilterCriteria x) => x.DateRanked.Min }, + new object[] { "ranked=2012-03-05", dateTimeOffsetFromDateOnly(2012, 3, 6), (FilterCriteria x) => x.DateRanked.Max }, }; [Test] @@ -688,13 +688,13 @@ namespace osu.Game.Tests.NonVisual.Filtering private static readonly object[] submitted_date_test_cases = { - new object[] { "submitted<2012", true }, - new object[] { "submitted<2012.03", true }, - new object[] { "submitted<2012/03/05", true }, - new object[] { "submitted<2012-3-5", true }, + new object[] { "submitted<2012", true }, + new object[] { "submitted<2012.03", true }, + new object[] { "submitted<2012/03/05", true }, + new object[] { "submitted<2012-3-5", true }, - new object[] { "submitted<0", false }, - new object[] { "submitted=99999", false }, + new object[] { "submitted<0", false }, + new object[] { "submitted=99999", false }, new object[] { "submitted>=2012-03-05-04", false }, new object[] { "submitted>=2012/03.05-04", false }, }; diff --git a/osu.Game/Screens/Select/FilterQueryParser.cs b/osu.Game/Screens/Select/FilterQueryParser.cs index d6f46c0fbd..9f29459012 100644 --- a/osu.Game/Screens/Select/FilterQueryParser.cs +++ b/osu.Game/Screens/Select/FilterQueryParser.cs @@ -642,7 +642,6 @@ namespace osu.Game.Screens.Select switch (key) { - case @"year": year = (int)value; break; @@ -670,14 +669,8 @@ namespace osu.Game.Screens.Select switch (op) { case Operator.Less: - if (month == null) - { - month = 1; - day = 1; - } - - if (day == null) - day = 1; + month ??= 1; + day ??= 1; dateTimeOffset = dateTimeOffsetFromDateOnly(year.Value, month.Value, day.Value); return tryUpdateCriteriaRange(ref dateRange, op, dateTimeOffset); @@ -690,6 +683,7 @@ namespace osu.Game.Screens.Select dateTimeOffset = dateTimeOffsetFromDateOnly(year.Value, month.Value, day.Value).AddYears(1); return tryUpdateCriteriaRange(ref dateRange, Operator.Less, dateTimeOffset); } + if (day == null) { day = 1; @@ -701,14 +695,8 @@ namespace osu.Game.Screens.Select return tryUpdateCriteriaRange(ref dateRange, Operator.Less, dateTimeOffset); case Operator.GreaterOrEqual: - if (month == null) - { - month = 1; - day = 1; - } - - if (day == null) - day = 1; + month ??= 1; + day ??= 1; dateTimeOffset = dateTimeOffsetFromDateOnly(year.Value, month.Value, day.Value); return tryUpdateCriteriaRange(ref dateRange, op, dateTimeOffset); @@ -721,6 +709,7 @@ namespace osu.Game.Screens.Select dateTimeOffset = dateTimeOffsetFromDateOnly(year.Value, month.Value, day.Value).AddYears(1); return tryUpdateCriteriaRange(ref dateRange, Operator.GreaterOrEqual, dateTimeOffset); } + if (day == null) { day = 1; @@ -742,11 +731,8 @@ namespace osu.Game.Screens.Select day = 1; minDateTimeOffset = dateTimeOffsetFromDateOnly(year.Value, month.Value, day.Value); maxDateTimeOffset = dateTimeOffsetFromDateOnly(year.Value, month.Value, day.Value).AddYears(1); - return ( - tryUpdateCriteriaRange(ref dateRange, Operator.GreaterOrEqual, minDateTimeOffset) - && - tryUpdateCriteriaRange(ref dateRange, Operator.Less, maxDateTimeOffset) - ); + return tryUpdateCriteriaRange(ref dateRange, Operator.GreaterOrEqual, minDateTimeOffset) + && tryUpdateCriteriaRange(ref dateRange, Operator.Less, maxDateTimeOffset); } if (day == null) @@ -754,21 +740,15 @@ namespace osu.Game.Screens.Select day = 1; minDateTimeOffset = dateTimeOffsetFromDateOnly(year.Value, month.Value, day.Value); maxDateTimeOffset = dateTimeOffsetFromDateOnly(year.Value, month.Value, day.Value).AddMonths(1); - return ( - tryUpdateCriteriaRange(ref dateRange, Operator.GreaterOrEqual, minDateTimeOffset) - && - tryUpdateCriteriaRange(ref dateRange, Operator.Less, maxDateTimeOffset) - ); + return tryUpdateCriteriaRange(ref dateRange, Operator.GreaterOrEqual, minDateTimeOffset) + && tryUpdateCriteriaRange(ref dateRange, Operator.Less, maxDateTimeOffset); } minDateTimeOffset = dateTimeOffsetFromDateOnly(year.Value, month.Value, day.Value); maxDateTimeOffset = dateTimeOffsetFromDateOnly(year.Value, month.Value, day.Value).AddDays(1); + return tryUpdateCriteriaRange(ref dateRange, Operator.GreaterOrEqual, minDateTimeOffset) + && tryUpdateCriteriaRange(ref dateRange, Operator.Less, maxDateTimeOffset); - return ( - tryUpdateCriteriaRange(ref dateRange, Operator.GreaterOrEqual, minDateTimeOffset) - && - tryUpdateCriteriaRange(ref dateRange, Operator.Less, maxDateTimeOffset) - ); default: return false; } From eb2f1d09f87d6b6dd296ac85e2c46f576304c5af Mon Sep 17 00:00:00 2001 From: WitherFlower <52672543+WitherFlower@users.noreply.github.com> Date: Thu, 10 Oct 2024 17:42:43 +0200 Subject: [PATCH 09/75] Improve regex readability by using character set --- osu.Game/Screens/Select/FilterQueryParser.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/osu.Game/Screens/Select/FilterQueryParser.cs b/osu.Game/Screens/Select/FilterQueryParser.cs index 9f29459012..ccffd34dc2 100644 --- a/osu.Game/Screens/Select/FilterQueryParser.cs +++ b/osu.Game/Screens/Select/FilterQueryParser.cs @@ -617,9 +617,7 @@ namespace osu.Game.Screens.Select /// The string value to attempt parsing for. private static bool tryUpdateRankedDateRange(ref FilterCriteria.OptionalRange dateRange, Operator op, string val) { - GroupCollection? match = null; - - match ??= tryMatchRegex(val, @"^(?\d+)((-|/|\.)(?\d+)((-|/|\.)(?\d+))?)?$"); + GroupCollection? match = tryMatchRegex(val, @"^(?\d+)([-/.](?\d+)([-/.](?\d+))?)?$"); if (match == null) return false; From da376c534b6c5e2e7f3f8cb380f6bddd9a7496f6 Mon Sep 17 00:00:00 2001 From: WitherFlower <52672543+WitherFlower@users.noreply.github.com> Date: Fri, 11 Oct 2024 09:49:47 +0200 Subject: [PATCH 10/75] Filter out unranked/unsubmitted beatmaps when using the ranked/submitted search filters --- osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs index aa064f97c9..3947cefb91 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs @@ -66,8 +66,8 @@ namespace osu.Game.Screens.Select.Carousel match &= !criteria.OverallDifficulty.HasFilter || criteria.OverallDifficulty.IsInRange(BeatmapInfo.Difficulty.OverallDifficulty); match &= !criteria.Length.HasFilter || criteria.Length.IsInRange(BeatmapInfo.Length); match &= !criteria.LastPlayed.HasFilter || criteria.LastPlayed.IsInRange(BeatmapInfo.LastPlayed ?? DateTimeOffset.MinValue); - match &= !criteria.DateRanked.HasFilter || criteria.DateRanked.IsInRange(BeatmapInfo.BeatmapSet?.DateRanked ?? DateTimeOffset.MinValue); - match &= !criteria.DateSubmitted.HasFilter || criteria.DateSubmitted.IsInRange(BeatmapInfo.BeatmapSet?.DateSubmitted ?? DateTimeOffset.MinValue); + match &= !criteria.DateRanked.HasFilter || (BeatmapInfo.BeatmapSet?.DateRanked != null && criteria.DateRanked.IsInRange(BeatmapInfo.BeatmapSet.DateRanked.Value)); + match &= !criteria.DateSubmitted.HasFilter || (BeatmapInfo.BeatmapSet?.DateSubmitted != null && criteria.DateSubmitted.IsInRange(BeatmapInfo.BeatmapSet.DateSubmitted.Value)); match &= !criteria.BPM.HasFilter || criteria.BPM.IsInRange(BeatmapInfo.BPM); match &= !criteria.BeatDivisor.HasFilter || criteria.BeatDivisor.IsInRange(BeatmapInfo.BeatDivisor); From d98cc7fe66bfde99eb7f4d173a36935b5f0d106d Mon Sep 17 00:00:00 2001 From: OliBomby Date: Sun, 31 Dec 2023 02:55:47 +0100 Subject: [PATCH 11/75] use G to change grid type --- osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs b/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs index 2b88860cc8..f66e16a236 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs @@ -25,6 +25,10 @@ namespace osu.Game.Rulesets.Osu.Edit { public partial class OsuGridToolboxGroup : EditorToolboxGroup, IKeyBindingHandler { + private static readonly PositionSnapGridType[] grid_types = Enum.GetValues(typeof(PositionSnapGridType)).Cast().ToArray(); + + private int currentGridTypeIndex; + [Resolved] private EditorBeatmap editorBeatmap { get; set; } = null!; @@ -246,6 +250,13 @@ namespace osu.Game.Rulesets.Osu.Edit Spacing.Value = Spacing.Value * 2 >= max_automatic_spacing ? Spacing.Value / 8 : Spacing.Value * 2; } + private void nextGridType() + { + currentGridTypeIndex = (currentGridTypeIndex + 1) % grid_types.Length; + GridType.Value = grid_types[currentGridTypeIndex]; + gridTypeButtons.Items[currentGridTypeIndex].Select(); + } + public bool OnPressed(KeyBindingPressEvent e) { switch (e.Action) From bfe78ff3a0ddabbc3a6f32dcabb2daaf771b6d78 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Sun, 31 Dec 2023 19:53:32 +0100 Subject: [PATCH 12/75] fix grid test --- .../Editor/TestSceneOsuEditorGrids.cs | 21 +++++++------------ 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditorGrids.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditorGrids.cs index b70ecfbba8..135e06d965 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditorGrids.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditorGrids.cs @@ -168,26 +168,21 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor } [Test] - public void TestGridSizeToggling() + public void TestGridTypeToggling() { AddStep("enable rectangular grid", () => InputManager.Key(Key.T)); AddUntilStep("rectangular grid visible", () => this.ChildrenOfType().Any()); - gridSizeIs(4); + gridActive(true); - nextGridSizeIs(8); - nextGridSizeIs(16); - nextGridSizeIs(32); - nextGridSizeIs(4); + nextGridTypeIs(); + nextGridTypeIs(); + nextGridTypeIs(); } - private void nextGridSizeIs(int size) + private void nextGridTypeIs() where T : PositionSnapGrid { - AddStep("toggle to next grid size", () => InputManager.Key(Key.G)); - gridSizeIs(size); + AddStep("toggle to next grid type", () => InputManager.Key(Key.G)); + gridActive(true); } - - private void gridSizeIs(int size) - => AddAssert($"grid size is {size}", () => this.ChildrenOfType().Single().Spacing.Value == new Vector2(size) - && EditorBeatmap.BeatmapInfo.GridSize == size); } } From e56a9d2ad436ef37bcb795e9a7e116c757e4a065 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Sun, 31 Dec 2023 20:25:34 +0100 Subject: [PATCH 13/75] Add TestGridFromPoints --- .../Editor/TestSceneOsuEditorGrids.cs | 69 ++++++++++++++++++- 1 file changed, 68 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditorGrids.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditorGrids.cs index 135e06d965..9a8e5f8cbd 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditorGrids.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditorGrids.cs @@ -9,6 +9,7 @@ using osu.Framework.Utils; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Osu.Edit; using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles; +using osu.Game.Rulesets.Osu.Objects; using osu.Game.Screens.Edit.Compose.Components; using osu.Game.Tests.Visual; using osu.Game.Utils; @@ -161,7 +162,8 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor return grid switch { RectangularPositionSnapGrid rectangular => rectangular.StartPosition.Value + GeometryUtils.RotateVector(rectangular.Spacing.Value, -rectangular.GridLineRotation.Value), - TriangularPositionSnapGrid triangular => triangular.StartPosition.Value + GeometryUtils.RotateVector(new Vector2(triangular.Spacing.Value / 2, triangular.Spacing.Value / 2 * MathF.Sqrt(3)), -triangular.GridLineRotation.Value), + TriangularPositionSnapGrid triangular => triangular.StartPosition.Value + GeometryUtils.RotateVector( + new Vector2(triangular.Spacing.Value / 2, triangular.Spacing.Value / 2 * MathF.Sqrt(3)), -triangular.GridLineRotation.Value), CircularPositionSnapGrid circular => circular.StartPosition.Value + GeometryUtils.RotateVector(new Vector2(circular.Spacing.Value, 0), -45), _ => Vector2.Zero }; @@ -184,5 +186,70 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor AddStep("toggle to next grid type", () => InputManager.Key(Key.G)); gridActive(true); } + + [Test] + public void TestGridFromPoints() + { + AddStep("enable rectangular grid", () => InputManager.Key(Key.Y)); + + AddStep("initiate grid from points", () => InputManager.Key(Key.B)); + AddStep("move cursor to slider head + (1, 1)", () => + { + var composer = Editor.ChildrenOfType().Single(); + InputManager.MoveMouseTo(composer.ToScreenSpace(((Slider)EditorBeatmap.HitObjects.First()).Position + new Vector2(1, 1))); + }); + AddStep("left click", () => InputManager.Click(MouseButton.Left)); + AddStep("move cursor to slider tail + (1, 1)", () => + { + var composer = Editor.ChildrenOfType().Single(); + InputManager.MoveMouseTo(composer.ToScreenSpace(((Slider)EditorBeatmap.HitObjects.First()).EndPosition + new Vector2(1, 1))); + }); + AddStep("left click", () => InputManager.Click(MouseButton.Left)); + + gridActive(true); + AddAssert("grid position at slider head", () => + { + var composer = Editor.ChildrenOfType().Single(); + return Precision.AlmostEquals(((Slider)EditorBeatmap.HitObjects.First()).Position, composer.StartPosition.Value); + }); + AddAssert("grid spacing is distance to slider tail", () => + { + var composer = Editor.ChildrenOfType().Single(); + return Precision.AlmostEquals(composer.Spacing.Value.X, 32.05, 0.01) + && Precision.AlmostEquals(composer.Spacing.Value.X, composer.Spacing.Value.Y); + }); + AddAssert("grid rotation points to slider tail", () => + { + var composer = Editor.ChildrenOfType().Single(); + return Precision.AlmostEquals(composer.GridLineRotation.Value, 0.09, 0.01); + }); + + AddStep("initiate grid from points", () => InputManager.Key(Key.B)); + AddStep("move cursor to slider tail + (1, 1)", () => + { + var composer = Editor.ChildrenOfType().Single(); + InputManager.MoveMouseTo(composer.ToScreenSpace(((Slider)EditorBeatmap.HitObjects.First()).EndPosition + new Vector2(1, 1))); + }); + AddStep("right click", () => InputManager.Click(MouseButton.Right)); + AddStep("move cursor to (0, 0)", () => + { + var composer = Editor.ChildrenOfType().Single(); + InputManager.MoveMouseTo(composer.ToScreenSpace(Vector2.Zero)); + }); + + gridActive(true); + AddAssert("grid position at slider tail", () => + { + var composer = Editor.ChildrenOfType().Single(); + return Precision.AlmostEquals(((Slider)EditorBeatmap.HitObjects.First()).EndPosition, composer.StartPosition.Value); + }); + AddAssert("grid spacing and rotation unchanged", () => + { + var composer = Editor.ChildrenOfType().Single(); + return Precision.AlmostEquals(composer.Spacing.Value.X, 32.05, 0.01) + && Precision.AlmostEquals(composer.Spacing.Value.X, composer.Spacing.Value.Y) + && Precision.AlmostEquals(composer.GridLineRotation.Value, 0.09, 0.01); + }); + } } } From b93bc21e45f7dce08f16579284ef50db2e3ebe6e Mon Sep 17 00:00:00 2001 From: OliBomby Date: Sun, 31 Dec 2023 21:57:11 +0100 Subject: [PATCH 14/75] Add back the old keybind for cycling grid spacing --- .../Editor/TestSceneOsuEditorGrids.cs | 25 ++++++++++++++++++- .../Edit/OsuGridToolboxGroup.cs | 4 +++ .../Input/Bindings/GlobalActionContainer.cs | 4 +++ .../GlobalActionKeyBindingStrings.cs | 5 ++++ 4 files changed, 37 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditorGrids.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditorGrids.cs index 9a8e5f8cbd..6025e98e2d 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditorGrids.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditorGrids.cs @@ -169,6 +169,29 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor }; } + [Test] + public void TestGridSizeToggling() + { + AddStep("enable rectangular grid", () => InputManager.Key(Key.Y)); + AddUntilStep("rectangular grid visible", () => this.ChildrenOfType().Any()); + gridSizeIs(4); + + nextGridSizeIs(8); + nextGridSizeIs(16); + nextGridSizeIs(32); + nextGridSizeIs(4); + } + + private void nextGridSizeIs(int size) + { + AddStep("toggle to next grid size", () => InputManager.Key(Key.G)); + gridSizeIs(size); + } + + private void gridSizeIs(int size) + => AddAssert($"grid size is {size}", () => this.ChildrenOfType().Single().Spacing.Value == new Vector2(size) + && EditorBeatmap.BeatmapInfo.GridSize == size); + [Test] public void TestGridTypeToggling() { @@ -183,7 +206,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor private void nextGridTypeIs() where T : PositionSnapGrid { - AddStep("toggle to next grid type", () => InputManager.Key(Key.G)); + AddStep("toggle to next grid type", () => InputManager.Key(Key.H)); gridActive(true); } diff --git a/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs b/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs index f66e16a236..707611995f 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs @@ -264,6 +264,10 @@ namespace osu.Game.Rulesets.Osu.Edit case GlobalAction.EditorCycleGridDisplayMode: nextGridSize(); return true; + + case GlobalAction.EditorCycleGridType: + nextGridType(); + return true; } return false; diff --git a/osu.Game/Input/Bindings/GlobalActionContainer.cs b/osu.Game/Input/Bindings/GlobalActionContainer.cs index aca0984e0f..82fde189a1 100644 --- a/osu.Game/Input/Bindings/GlobalActionContainer.cs +++ b/osu.Game/Input/Bindings/GlobalActionContainer.cs @@ -135,6 +135,7 @@ namespace osu.Game.Input.Bindings new KeyBinding(new[] { InputKey.J }, GlobalAction.EditorNudgeLeft), new KeyBinding(new[] { InputKey.K }, GlobalAction.EditorNudgeRight), new KeyBinding(new[] { InputKey.G }, GlobalAction.EditorCycleGridDisplayMode), + new KeyBinding(new[] { InputKey.H }, GlobalAction.EditorCycleGridType), new KeyBinding(new[] { InputKey.F5 }, GlobalAction.EditorTestGameplay), new KeyBinding(new[] { InputKey.T }, GlobalAction.EditorTapForBPM), new KeyBinding(new[] { InputKey.Control, InputKey.H }, GlobalAction.EditorFlipHorizontally), @@ -371,6 +372,9 @@ namespace osu.Game.Input.Bindings [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.EditorCycleGridDisplayMode))] EditorCycleGridDisplayMode, + [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.EditorCycleGridType))] + EditorCycleGridType, + [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.EditorTestGameplay))] EditorTestGameplay, diff --git a/osu.Game/Localisation/GlobalActionKeyBindingStrings.cs b/osu.Game/Localisation/GlobalActionKeyBindingStrings.cs index 206db1a166..54379ca598 100644 --- a/osu.Game/Localisation/GlobalActionKeyBindingStrings.cs +++ b/osu.Game/Localisation/GlobalActionKeyBindingStrings.cs @@ -194,6 +194,11 @@ namespace osu.Game.Localisation /// public static LocalisableString EditorCycleGridDisplayMode => new TranslatableString(getKey(@"editor_cycle_grid_display_mode"), @"Cycle grid display mode"); + /// + /// "Cycle grid type" + /// + public static LocalisableString EditorCycleGridType => new TranslatableString(getKey(@"editor_cycle_grid_type"), @"Cycle grid type"); + /// /// "Test gameplay" /// From ada20d230a1a1866ec550455e89bdb4b1668b908 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Mon, 1 Jan 2024 16:28:35 +0100 Subject: [PATCH 15/75] Fix grid type cycling not taking into account the radio button selection --- osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs b/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs index 707611995f..0e7d0c7531 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs @@ -27,8 +27,6 @@ namespace osu.Game.Rulesets.Osu.Edit { private static readonly PositionSnapGridType[] grid_types = Enum.GetValues(typeof(PositionSnapGridType)).Cast().ToArray(); - private int currentGridTypeIndex; - [Resolved] private EditorBeatmap editorBeatmap { get; set; } = null!; @@ -252,9 +250,9 @@ namespace osu.Game.Rulesets.Osu.Edit private void nextGridType() { - currentGridTypeIndex = (currentGridTypeIndex + 1) % grid_types.Length; - GridType.Value = grid_types[currentGridTypeIndex]; - gridTypeButtons.Items[currentGridTypeIndex].Select(); + int nextGridTypeIndex = (Array.IndexOf(grid_types, GridType.Value) + 1) % grid_types.Length; + GridType.Value = grid_types[nextGridTypeIndex]; + gridTypeButtons.Items[nextGridTypeIndex].Select(); } public bool OnPressed(KeyBindingPressEvent e) From 56bfcde446f95c424718616d83f864022bca9551 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Thu, 10 Oct 2024 19:37:54 +0200 Subject: [PATCH 16/75] Update grid placement tool test I somehow missed this test when splitting up PRs so here it is now --- .../Editor/TestSceneOsuEditorGrids.cs | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditorGrids.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditorGrids.cs index 6025e98e2d..9d6135bbf8 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditorGrids.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditorGrids.cs @@ -211,11 +211,11 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor } [Test] - public void TestGridFromPoints() + public void TestGridPlacementTool() { - AddStep("enable rectangular grid", () => InputManager.Key(Key.Y)); + AddStep("enable rectangular grid", () => InputManager.Key(Key.T)); - AddStep("initiate grid from points", () => InputManager.Key(Key.B)); + AddStep("start grid placement", () => InputManager.Key(Key.Number5)); AddStep("move cursor to slider head + (1, 1)", () => { var composer = Editor.ChildrenOfType().Single(); @@ -247,13 +247,17 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor return Precision.AlmostEquals(composer.GridLineRotation.Value, 0.09, 0.01); }); - AddStep("initiate grid from points", () => InputManager.Key(Key.B)); + AddStep("start grid placement", () => InputManager.Key(Key.Number5)); AddStep("move cursor to slider tail + (1, 1)", () => { var composer = Editor.ChildrenOfType().Single(); InputManager.MoveMouseTo(composer.ToScreenSpace(((Slider)EditorBeatmap.HitObjects.First()).EndPosition + new Vector2(1, 1))); }); - AddStep("right click", () => InputManager.Click(MouseButton.Right)); + AddStep("double click", () => + { + InputManager.Click(MouseButton.Left); + InputManager.Click(MouseButton.Left); + }); AddStep("move cursor to (0, 0)", () => { var composer = Editor.ChildrenOfType().Single(); From 550e5752219545a921863d6552223c8eee47f734 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Thu, 10 Oct 2024 19:42:59 +0200 Subject: [PATCH 17/75] Rename "Cycle grid display mode" to "Cycle grid spacing" The "display mode" is easy to confuse with grid type, so I renamed it to literally the grid property it affects. --- osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs | 2 +- osu.Game/Input/Bindings/GlobalActionContainer.cs | 6 +++--- osu.Game/Localisation/GlobalActionKeyBindingStrings.cs | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs b/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs index 0e7d0c7531..7280bf7b6e 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs @@ -259,7 +259,7 @@ namespace osu.Game.Rulesets.Osu.Edit { switch (e.Action) { - case GlobalAction.EditorCycleGridDisplayMode: + case GlobalAction.EditorCycleGridSpacing: nextGridSize(); return true; diff --git a/osu.Game/Input/Bindings/GlobalActionContainer.cs b/osu.Game/Input/Bindings/GlobalActionContainer.cs index 82fde189a1..2b5f41126a 100644 --- a/osu.Game/Input/Bindings/GlobalActionContainer.cs +++ b/osu.Game/Input/Bindings/GlobalActionContainer.cs @@ -134,7 +134,7 @@ namespace osu.Game.Input.Bindings new KeyBinding(new[] { InputKey.Control, InputKey.D }, GlobalAction.EditorCloneSelection), new KeyBinding(new[] { InputKey.J }, GlobalAction.EditorNudgeLeft), new KeyBinding(new[] { InputKey.K }, GlobalAction.EditorNudgeRight), - new KeyBinding(new[] { InputKey.G }, GlobalAction.EditorCycleGridDisplayMode), + new KeyBinding(new[] { InputKey.G }, GlobalAction.EditorCycleGridSpacing), new KeyBinding(new[] { InputKey.H }, GlobalAction.EditorCycleGridType), new KeyBinding(new[] { InputKey.F5 }, GlobalAction.EditorTestGameplay), new KeyBinding(new[] { InputKey.T }, GlobalAction.EditorTapForBPM), @@ -369,8 +369,8 @@ namespace osu.Game.Input.Bindings [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.ToggleChatFocus))] ToggleChatFocus, - [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.EditorCycleGridDisplayMode))] - EditorCycleGridDisplayMode, + [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.EditorCycleGridSpacing))] + EditorCycleGridSpacing, [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.EditorCycleGridType))] EditorCycleGridType, diff --git a/osu.Game/Localisation/GlobalActionKeyBindingStrings.cs b/osu.Game/Localisation/GlobalActionKeyBindingStrings.cs index 54379ca598..ed80704a0a 100644 --- a/osu.Game/Localisation/GlobalActionKeyBindingStrings.cs +++ b/osu.Game/Localisation/GlobalActionKeyBindingStrings.cs @@ -190,9 +190,9 @@ namespace osu.Game.Localisation public static LocalisableString EditorCloneSelection => new TranslatableString(getKey(@"editor_clone_selection"), @"Clone selection"); /// - /// "Cycle grid display mode" + /// "Cycle grid spacing" /// - public static LocalisableString EditorCycleGridDisplayMode => new TranslatableString(getKey(@"editor_cycle_grid_display_mode"), @"Cycle grid display mode"); + public static LocalisableString EditorCycleGridSpacing => new TranslatableString(getKey(@"editor_cycle_grid_spacing"), @"Cycle grid spacing"); /// /// "Cycle grid type" From d1da1d4079af62bb8fc5433597081b7f55d1dab1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 11 Oct 2024 15:03:30 +0200 Subject: [PATCH 18/75] Rename methods to fit new changes better --- osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs | 4 ++-- osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs b/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs index 8e94112866..e3ab95c402 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs @@ -240,12 +240,12 @@ namespace osu.Game.Rulesets.Osu.Edit points = originalConvexHull!; foreach (var point in points) - scale = clampToBound(scale, point, Vector2.Zero, OsuPlayfield.BASE_SIZE); + scale = clampToBounds(scale, point, Vector2.Zero, OsuPlayfield.BASE_SIZE); return scale; // Clamps the scale vector s such that the point p scaled by s is within the rectangle defined by lowerBounds and upperBounds - Vector2 clampToBound(Vector2 s, Vector2 p, Vector2 lowerBounds, Vector2 upperBounds) + Vector2 clampToBounds(Vector2 s, Vector2 p, Vector2 lowerBounds, Vector2 upperBounds) { p -= actualOrigin; lowerBounds -= actualOrigin; diff --git a/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs b/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs index cf4473bd41..c6c0a4d3a8 100644 --- a/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs +++ b/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs @@ -175,7 +175,7 @@ namespace osu.Game.Rulesets.Osu.Edit axisBindable.Disabled = !available; } - private void updateMaxScale() + private void updateMinMaxScale() { if (!scaleHandler.OriginalSurroundingQuad.HasValue) return; @@ -205,7 +205,7 @@ namespace osu.Game.Rulesets.Osu.Edit private void setOrigin(ScaleOrigin origin) { scaleInfo.Value = scaleInfo.Value with { Origin = origin }; - updateMaxScale(); + updateMinMaxScale(); updateAxisCheckBoxesEnabled(); } @@ -237,14 +237,14 @@ namespace osu.Game.Rulesets.Osu.Edit private void setAxis(bool x, bool y) { scaleInfo.Value = scaleInfo.Value with { XAxis = x, YAxis = y }; - updateMaxScale(); + updateMinMaxScale(); } protected override void PopIn() { base.PopIn(); scaleHandler.Begin(); - updateMaxScale(); + updateMinMaxScale(); } protected override void PopOut() From 4e8e4a34bdd1a8eb43eb95aae542328ab3aee74c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 11 Oct 2024 15:18:43 +0200 Subject: [PATCH 19/75] Fix scale popover doing things when both scale axes are turned off Spotted in passing when reviewing https://github.com/ppy/osu/pull/30080. The popover would very arbitrarily revert to scaling by Y axis if both checkboxes were checked off. Not sure how this passed review. --- osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs b/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs index 33b0c14185..25515a90d5 100644 --- a/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs +++ b/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs @@ -219,7 +219,18 @@ namespace osu.Game.Rulesets.Osu.Edit } } - private Axes getAdjustAxis(PreciseScaleInfo scale) => scale.XAxis ? scale.YAxis ? Axes.Both : Axes.X : Axes.Y; + private Axes getAdjustAxis(PreciseScaleInfo scale) + { + var result = Axes.None; + + if (scale.XAxis) + result |= Axes.X; + + if (scale.YAxis) + result |= Axes.Y; + + return result; + } private float getRotation(PreciseScaleInfo scale) => scale.Origin == ScaleOrigin.GridCentre ? gridToolbox.GridLinesRotation.Value : 0; From d07a2fbb57cf7ca18054fb8a2587f659e7312886 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 12 Oct 2024 02:18:08 +0900 Subject: [PATCH 20/75] Change shortcut to `Shift`+`G` --- osu.Game/Input/Bindings/GlobalActionContainer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Input/Bindings/GlobalActionContainer.cs b/osu.Game/Input/Bindings/GlobalActionContainer.cs index 2b5f41126a..f7ba099705 100644 --- a/osu.Game/Input/Bindings/GlobalActionContainer.cs +++ b/osu.Game/Input/Bindings/GlobalActionContainer.cs @@ -135,7 +135,7 @@ namespace osu.Game.Input.Bindings new KeyBinding(new[] { InputKey.J }, GlobalAction.EditorNudgeLeft), new KeyBinding(new[] { InputKey.K }, GlobalAction.EditorNudgeRight), new KeyBinding(new[] { InputKey.G }, GlobalAction.EditorCycleGridSpacing), - new KeyBinding(new[] { InputKey.H }, GlobalAction.EditorCycleGridType), + new KeyBinding(new[] { InputKey.Shift, InputKey.G }, GlobalAction.EditorCycleGridType), new KeyBinding(new[] { InputKey.F5 }, GlobalAction.EditorTestGameplay), new KeyBinding(new[] { InputKey.T }, GlobalAction.EditorTapForBPM), new KeyBinding(new[] { InputKey.Control, InputKey.H }, GlobalAction.EditorFlipHorizontally), From 05c8b3cbceaf761a4ac5bd140c3be810ef6fdbf4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 12 Oct 2024 02:18:21 +0900 Subject: [PATCH 21/75] Simplify cycle logic --- .../Edit/OsuGridToolboxGroup.cs | 18 ++++-------------- 1 file changed, 4 insertions(+), 14 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs b/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs index 7280bf7b6e..4d1e06c857 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs @@ -215,6 +215,8 @@ namespace osu.Game.Rulesets.Osu.Edit { GridLinesRotation.Disabled = v.NewValue == PositionSnapGridType.Circle; + gridTypeButtons.Items[(int)v.NewValue].Select(); + switch (v.NewValue) { case PositionSnapGridType.Square: @@ -243,28 +245,16 @@ namespace osu.Game.Rulesets.Osu.Edit return ((rotation + 360 + period * 0.5f) % period) - period * 0.5f; } - private void nextGridSize() - { - Spacing.Value = Spacing.Value * 2 >= max_automatic_spacing ? Spacing.Value / 8 : Spacing.Value * 2; - } - - private void nextGridType() - { - int nextGridTypeIndex = (Array.IndexOf(grid_types, GridType.Value) + 1) % grid_types.Length; - GridType.Value = grid_types[nextGridTypeIndex]; - gridTypeButtons.Items[nextGridTypeIndex].Select(); - } - public bool OnPressed(KeyBindingPressEvent e) { switch (e.Action) { case GlobalAction.EditorCycleGridSpacing: - nextGridSize(); + Spacing.Value = Spacing.Value * 2 >= max_automatic_spacing ? Spacing.Value / 8 : Spacing.Value * 2; return true; case GlobalAction.EditorCycleGridType: - nextGridType(); + GridType.Value = (PositionSnapGridType)(((int)GridType.Value + 1) % Enum.GetValues().Length); return true; } From b62d9f8696206b9779c7d7286d974cd4e41fe581 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 12 Oct 2024 02:19:02 +0900 Subject: [PATCH 22/75] Fix bindings being clobbered --- osu.Game/Input/Bindings/GlobalActionContainer.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Input/Bindings/GlobalActionContainer.cs b/osu.Game/Input/Bindings/GlobalActionContainer.cs index f7ba099705..77c21e3703 100644 --- a/osu.Game/Input/Bindings/GlobalActionContainer.cs +++ b/osu.Game/Input/Bindings/GlobalActionContainer.cs @@ -372,9 +372,6 @@ namespace osu.Game.Input.Bindings [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.EditorCycleGridSpacing))] EditorCycleGridSpacing, - [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.EditorCycleGridType))] - EditorCycleGridType, - [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.EditorTestGameplay))] EditorTestGameplay, @@ -476,6 +473,9 @@ namespace osu.Game.Input.Bindings [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.EditorSeekToNextSamplePoint))] EditorSeekToNextSamplePoint, + + [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.EditorCycleGridType))] + EditorCycleGridType, } public enum GlobalActionCategory From 8b046f60f0f81cd333d85979b1fd7add2f09be99 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 12 Oct 2024 02:21:28 +0900 Subject: [PATCH 23/75] Update test --- .../Editor/TestSceneOsuEditorGrids.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditorGrids.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditorGrids.cs index 9d6135bbf8..f666812082 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditorGrids.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditorGrids.cs @@ -206,7 +206,12 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor private void nextGridTypeIs() where T : PositionSnapGrid { - AddStep("toggle to next grid type", () => InputManager.Key(Key.H)); + AddStep("toggle to next grid type", () => + { + InputManager.PressKey(Key.ShiftLeft); + InputManager.Key(Key.G); + InputManager.ReleaseKey(Key.ShiftLeft); + }); gridActive(true); } From 9f73a45580c2452238aa80d313a8158da4b0c4a5 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Fri, 11 Oct 2024 23:57:26 +0200 Subject: [PATCH 24/75] Explicitly assert specific grid type --- osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditorGrids.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditorGrids.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditorGrids.cs index f666812082..fb109ba6f9 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditorGrids.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditorGrids.cs @@ -130,6 +130,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor private void gridActive(bool active) where T : PositionSnapGrid { + AddAssert($"grid type is {typeof(T).Name}", () => this.ChildrenOfType().Any()); AddStep("choose placement tool", () => InputManager.Key(Key.Number2)); AddStep("move cursor to spacing + (1, 1)", () => { From 1e7e2e0b1c76f5dcfd2a7ab76564910f1df0aaf6 Mon Sep 17 00:00:00 2001 From: schiavoanto Date: Sat, 12 Oct 2024 00:55:33 +0200 Subject: [PATCH 25/75] Add more localisation in skin editor --- osu.Game/Localisation/SkinEditorStrings.cs | 45 +++++++++++++++++++ osu.Game/Overlays/SkinEditor/SkinEditor.cs | 2 +- .../SkinEditor/SkinSelectionHandler.cs | 17 +++---- 3 files changed, 55 insertions(+), 9 deletions(-) diff --git a/osu.Game/Localisation/SkinEditorStrings.cs b/osu.Game/Localisation/SkinEditorStrings.cs index 3c1d1ff40d..d96ea7dd9f 100644 --- a/osu.Game/Localisation/SkinEditorStrings.cs +++ b/osu.Game/Localisation/SkinEditorStrings.cs @@ -49,6 +49,51 @@ namespace osu.Game.Localisation /// public static LocalisableString RevertToDefaultDescription => new TranslatableString(getKey(@"revert_to_default_description"), @"All layout elements for layers in the current screen will be reset to defaults."); + /// + /// "Closest" + /// + public static LocalisableString Closest => new TranslatableString(getKey(@"closest"), @"Closest"); + + /// + /// "Anchor" + /// + public static LocalisableString Anchor => new TranslatableString(getKey(@"anchor"), @"Anchor"); + + /// + /// "Origin" + /// + public static LocalisableString Origin => new TranslatableString(getKey(@"origin"), @"Origin"); + + /// + /// "Reset position" + /// + public static LocalisableString ResetPosition => new TranslatableString(getKey(@"reset_position"), @"Reset position"); + + /// + /// "Reset rotation" + /// + public static LocalisableString ResetRotation => new TranslatableString(getKey(@"reset_rotation"), @"Reset rotation"); + + /// + /// "Reset scale" + /// + public static LocalisableString ResetScale => new TranslatableString(getKey(@"reset_scale"), @"Reset scale"); + + /// + /// "Bring to front" + /// + public static LocalisableString BringToFront => new TranslatableString(getKey(@"bring_to_front"), @"Bring to front"); + + /// + /// "Send to back" + /// + public static LocalisableString SendToBack => new TranslatableString(getKey(@"send_to_back"), @"Send to back"); + + /// + /// "Current working layer" + /// + public static LocalisableString CurrentWorkingLayer => new TranslatableString(getKey(@"current_working_layer"), @"Current working layer"); + private static string getKey(string key) => $@"{prefix}:{key}"; } } diff --git a/osu.Game/Overlays/SkinEditor/SkinEditor.cs b/osu.Game/Overlays/SkinEditor/SkinEditor.cs index 6f7781ee9c..42908f7102 100644 --- a/osu.Game/Overlays/SkinEditor/SkinEditor.cs +++ b/osu.Game/Overlays/SkinEditor/SkinEditor.cs @@ -361,7 +361,7 @@ namespace osu.Game.Overlays.SkinEditor componentsSidebar.Children = new[] { - new EditorSidebarSection("Current working layer") + new EditorSidebarSection(SkinEditorStrings.CurrentWorkingLayer) { Children = new Drawable[] { diff --git a/osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs b/osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs index 722ffd6d07..f7691d07b3 100644 --- a/osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs +++ b/osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs @@ -13,6 +13,7 @@ using osu.Game.Extensions; using osu.Game.Graphics.UserInterface; using osu.Game.Rulesets.Edit; using osu.Game.Screens.Edit.Compose.Components; +using osu.Game.Localisation; using osu.Game.Skinning; using osu.Game.Utils; using osuTK; @@ -101,19 +102,19 @@ namespace osu.Game.Overlays.SkinEditor protected override IEnumerable GetContextMenuItemsForSelection(IEnumerable> selection) { - var closestItem = new TernaryStateRadioMenuItem("Closest", MenuItemType.Standard, _ => applyClosestAnchors()) + var closestItem = new TernaryStateRadioMenuItem(SkinEditorStrings.Closest.ToString(), MenuItemType.Standard, _ => applyClosestAnchors()) { State = { Value = GetStateFromSelection(selection, c => !c.Item.UsesFixedAnchor) } }; - yield return new OsuMenuItem("Anchor") + yield return new OsuMenuItem(SkinEditorStrings.Anchor) { Items = createAnchorItems((d, a) => d.UsesFixedAnchor && ((Drawable)d).Anchor == a, applyFixedAnchors) .Prepend(closestItem) .ToArray() }; - yield return originMenu = new OsuMenuItem("Origin"); + yield return originMenu = new OsuMenuItem(SkinEditorStrings.Origin); closestItem.State.BindValueChanged(s => { @@ -125,19 +126,19 @@ namespace osu.Game.Overlays.SkinEditor yield return new OsuMenuItemSpacer(); - yield return new OsuMenuItem("Reset position", MenuItemType.Standard, () => + yield return new OsuMenuItem(SkinEditorStrings.ResetPosition, MenuItemType.Standard, () => { foreach (var blueprint in SelectedBlueprints) ((Drawable)blueprint.Item).Position = Vector2.Zero; }); - yield return new OsuMenuItem("Reset rotation", MenuItemType.Standard, () => + yield return new OsuMenuItem(SkinEditorStrings.ResetRotation, MenuItemType.Standard, () => { foreach (var blueprint in SelectedBlueprints) ((Drawable)blueprint.Item).Rotation = 0; }); - yield return new OsuMenuItem("Reset scale", MenuItemType.Standard, () => + yield return new OsuMenuItem(SkinEditorStrings.ResetScale, MenuItemType.Standard, () => { foreach (var blueprint in SelectedBlueprints) { @@ -153,9 +154,9 @@ namespace osu.Game.Overlays.SkinEditor yield return new OsuMenuItemSpacer(); - yield return new OsuMenuItem("Bring to front", MenuItemType.Standard, () => skinEditor.BringSelectionToFront()); + yield return new OsuMenuItem(SkinEditorStrings.BringToFront, MenuItemType.Standard, () => skinEditor.BringSelectionToFront()); - yield return new OsuMenuItem("Send to back", MenuItemType.Standard, () => skinEditor.SendSelectionToBack()); + yield return new OsuMenuItem(SkinEditorStrings.SendToBack, MenuItemType.Standard, () => skinEditor.SendSelectionToBack()); yield return new OsuMenuItemSpacer(); From 9cd7f2b5d4c3a6506e4076b8cdbfecf37bbd89b5 Mon Sep 17 00:00:00 2001 From: schiavoanto Date: Sat, 12 Oct 2024 01:38:52 +0200 Subject: [PATCH 26/75] Use `LocalisableDescription` for skin editor layers dropdown --- osu.Game/Localisation/SkinEditorStrings.cs | 10 ++++++++++ osu.Game/Skinning/GlobalSkinnableContainers.cs | 9 +++++---- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/osu.Game/Localisation/SkinEditorStrings.cs b/osu.Game/Localisation/SkinEditorStrings.cs index d96ea7dd9f..9ae9aa0747 100644 --- a/osu.Game/Localisation/SkinEditorStrings.cs +++ b/osu.Game/Localisation/SkinEditorStrings.cs @@ -34,6 +34,16 @@ namespace osu.Game.Localisation /// public static LocalisableString Gameplay => new TranslatableString(getKey(@"gameplay"), @"Gameplay"); + /// + /// "HUD" + /// + public static LocalisableString HUD => new TranslatableString(getKey(@"hud"), @"HUD"); + + /// + /// "Playfield" + /// + public static LocalisableString Playfield => new TranslatableString(getKey(@"playfield"), @"Playfield"); + /// /// "Settings ({0})" /// diff --git a/osu.Game/Skinning/GlobalSkinnableContainers.cs b/osu.Game/Skinning/GlobalSkinnableContainers.cs index 02f915895f..73cb1303a0 100644 --- a/osu.Game/Skinning/GlobalSkinnableContainers.cs +++ b/osu.Game/Skinning/GlobalSkinnableContainers.cs @@ -1,7 +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 System.ComponentModel; +using osu.Framework.Localisation; +using osu.Game.Localisation; namespace osu.Game.Skinning { @@ -10,13 +11,13 @@ namespace osu.Game.Skinning /// public enum GlobalSkinnableContainers { - [Description("HUD")] + [LocalisableDescription(typeof(SkinEditorStrings), nameof(SkinEditorStrings.HUD))] MainHUDComponents, - [Description("Song select")] + [LocalisableDescription(typeof(SkinEditorStrings), nameof(SkinEditorStrings.SongSelect))] SongSelect, - [Description("Playfield")] + [LocalisableDescription(typeof(SkinEditorStrings), nameof(SkinEditorStrings.Playfield))] Playfield } } From fc1ebfdf64355b60c3f572ec00b1684ea2c82c31 Mon Sep 17 00:00:00 2001 From: schiavoanto Date: Sat, 12 Oct 2024 02:00:51 +0200 Subject: [PATCH 27/75] Fix layers dropdown localised entries --- osu.Game/Skinning/GlobalSkinnableContainerLookup.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Skinning/GlobalSkinnableContainerLookup.cs b/osu.Game/Skinning/GlobalSkinnableContainerLookup.cs index 6d78981f0a..33b5527917 100644 --- a/osu.Game/Skinning/GlobalSkinnableContainerLookup.cs +++ b/osu.Game/Skinning/GlobalSkinnableContainerLookup.cs @@ -31,9 +31,9 @@ namespace osu.Game.Skinning public override string ToString() { - if (Ruleset == null) return Lookup.GetDescription(); + if (Ruleset == null) return Lookup.GetLocalisableDescription().ToString(); - return $"{Lookup.GetDescription()} (\"{Ruleset.Name}\" only)"; + return $"{Lookup.GetLocalisableDescription().ToString()} (\"{Ruleset.Name}\" only)"; } public bool Equals(GlobalSkinnableContainerLookup? other) From 71b08b54c1ec31a25d6e2098919311e8c83dd79b Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Fri, 11 Oct 2024 18:48:04 -0700 Subject: [PATCH 28/75] Make `TernaryStateRadioMenuItem` localisable --- osu.Game/Graphics/UserInterface/TernaryStateMenuItem.cs | 3 ++- osu.Game/Graphics/UserInterface/TernaryStateRadioMenuItem.cs | 3 ++- osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/TernaryStateMenuItem.cs b/osu.Game/Graphics/UserInterface/TernaryStateMenuItem.cs index d2b6ff2dba..f98628a486 100644 --- a/osu.Game/Graphics/UserInterface/TernaryStateMenuItem.cs +++ b/osu.Game/Graphics/UserInterface/TernaryStateMenuItem.cs @@ -5,6 +5,7 @@ using System; using osu.Framework.Graphics.Sprites; +using osu.Framework.Localisation; namespace osu.Game.Graphics.UserInterface { @@ -20,7 +21,7 @@ namespace osu.Game.Graphics.UserInterface /// A function to inform what the next state should be when this item is clicked. /// The type of action which this performs. /// A delegate to be invoked when this is pressed. - protected TernaryStateMenuItem(string text, Func nextStateFunction, MenuItemType type = MenuItemType.Standard, Action action = null) + protected TernaryStateMenuItem(LocalisableString text, Func nextStateFunction, MenuItemType type = MenuItemType.Standard, Action action = null) : base(text, nextStateFunction, type, action) { } diff --git a/osu.Game/Graphics/UserInterface/TernaryStateRadioMenuItem.cs b/osu.Game/Graphics/UserInterface/TernaryStateRadioMenuItem.cs index 133362d3e6..30fea62cd7 100644 --- a/osu.Game/Graphics/UserInterface/TernaryStateRadioMenuItem.cs +++ b/osu.Game/Graphics/UserInterface/TernaryStateRadioMenuItem.cs @@ -4,6 +4,7 @@ #nullable disable using System; +using osu.Framework.Localisation; namespace osu.Game.Graphics.UserInterface { @@ -18,7 +19,7 @@ namespace osu.Game.Graphics.UserInterface /// The text to display. /// The type of action which this performs. /// A delegate to be invoked when this is pressed. - public TernaryStateRadioMenuItem(string text, MenuItemType type = MenuItemType.Standard, Action action = null) + public TernaryStateRadioMenuItem(LocalisableString text, MenuItemType type = MenuItemType.Standard, Action action = null) : base(text, getNextState, type, action) { } diff --git a/osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs b/osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs index f7691d07b3..bc878b9214 100644 --- a/osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs +++ b/osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs @@ -102,7 +102,7 @@ namespace osu.Game.Overlays.SkinEditor protected override IEnumerable GetContextMenuItemsForSelection(IEnumerable> selection) { - var closestItem = new TernaryStateRadioMenuItem(SkinEditorStrings.Closest.ToString(), MenuItemType.Standard, _ => applyClosestAnchors()) + var closestItem = new TernaryStateRadioMenuItem(SkinEditorStrings.Closest, MenuItemType.Standard, _ => applyClosestAnchors()) { State = { Value = GetStateFromSelection(selection, c => !c.Item.UsesFixedAnchor) } }; From e4f9c861bad6b192752eeb9d7feef3b3aa29d459 Mon Sep 17 00:00:00 2001 From: Artemis Rosman <73006620+rozukke@users.noreply.github.com> Date: Sun, 13 Oct 2024 00:50:45 +1100 Subject: [PATCH 29/75] Add functionality to mass reset offsets --- osu.Game/Beatmaps/BeatmapManager.cs | 16 ++++++++++++++++ .../DeleteConfirmationContentStrings.cs | 5 +++++ .../Localisation/MaintenanceSettingsStrings.cs | 5 +++++ .../Sections/Maintenance/BeatmapSettings.cs | 15 +++++++++++++++ 4 files changed, 41 insertions(+) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index cd818941ff..f72741af16 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -313,6 +313,22 @@ namespace osu.Game.Beatmaps }); } + public void ResetAllOffsets() + { + const string reset_complete_message = "All offsets have been reset!"; + Realm.Write(r => + { + var items = r.All(); + + foreach (var beatmap in items) + { + beatmap.UserSettings.Offset = 0.0; + } + + PostNotification?.Invoke(new ProgressCompletionNotification { Text = reset_complete_message }); + }); + } + public void Delete(Expression>? filter = null, bool silent = false) { Realm.Run(r => diff --git a/osu.Game/Localisation/DeleteConfirmationContentStrings.cs b/osu.Game/Localisation/DeleteConfirmationContentStrings.cs index d781fadbce..2b2f4dda54 100644 --- a/osu.Game/Localisation/DeleteConfirmationContentStrings.cs +++ b/osu.Game/Localisation/DeleteConfirmationContentStrings.cs @@ -19,6 +19,11 @@ namespace osu.Game.Localisation /// public static LocalisableString BeatmapVideos => new TranslatableString(getKey(@"beatmap_videos"), @"Are you sure you want to delete all beatmaps videos? This cannot be undone!"); + /// + /// "Are you sure you want to reset all local beatmap offsets? This cannot be undone!" + /// + public static LocalisableString Offsets => new TranslatableString(getKey(@"offsets"), @"Are you sure you want to reset all local beatmap offsets? This cannot be undone!"); + /// /// "Are you sure you want to delete all skins? This cannot be undone!" /// diff --git a/osu.Game/Localisation/MaintenanceSettingsStrings.cs b/osu.Game/Localisation/MaintenanceSettingsStrings.cs index 03e15e8393..6d5e0d5e0e 100644 --- a/osu.Game/Localisation/MaintenanceSettingsStrings.cs +++ b/osu.Game/Localisation/MaintenanceSettingsStrings.cs @@ -59,6 +59,11 @@ namespace osu.Game.Localisation /// public static LocalisableString DeleteAllBeatmapVideos => new TranslatableString(getKey(@"delete_all_beatmap_videos"), @"Delete ALL beatmap videos"); + /// + /// "Reset ALL beatmap offsets" + /// + public static LocalisableString ResetAllOffsets => new TranslatableString(getKey(@"reset_all_offsets"), @"Reset ALL beatmap offsets"); + /// /// "Delete ALL scores" /// diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/BeatmapSettings.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/BeatmapSettings.cs index d0a8fc7d2c..597e03fab2 100644 --- a/osu.Game/Overlays/Settings/Sections/Maintenance/BeatmapSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Maintenance/BeatmapSettings.cs @@ -16,6 +16,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance private SettingsButton deleteBeatmapsButton = null!; private SettingsButton deleteBeatmapVideosButton = null!; + private SettingsButton resetOffsetsButton = null!; private SettingsButton restoreButton = null!; private SettingsButton undeleteButton = null!; @@ -47,6 +48,20 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance }, DeleteConfirmationContentStrings.BeatmapVideos)); } }); + + Add(resetOffsetsButton = new DangerousSettingsButton + { + Text = MaintenanceSettingsStrings.ResetAllOffsets, + Action = () => + { + dialogOverlay?.Push(new MassDeleteConfirmationDialog(() => + { + resetOffsetsButton.Enabled.Value = false; + Task.Run(beatmaps.ResetAllOffsets).ContinueWith(_ => Schedule(() => resetOffsetsButton.Enabled.Value = true)); + }, DeleteConfirmationContentStrings.Offsets)); + } + }); + AddRange(new Drawable[] { restoreButton = new SettingsButton From dcd3e5194e3ba41e69fd69c34afb0c0e6243a48f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20Brandst=C3=B6tter?= Date: Sat, 12 Oct 2024 20:31:27 +0200 Subject: [PATCH 30/75] Group `HitResult`s with the same name into one column in beatmap ranking Closes #29911 --- .../Visual/Online/TestSceneScoresContainer.cs | 56 ++++++++++++++++++- .../Overlays/BeatmapSet/Scores/ScoreTable.cs | 34 ++++++----- .../BeatmapSet/Scores/ScoresContainer.cs | 12 ++-- 3 files changed, 79 insertions(+), 23 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneScoresContainer.cs b/osu.Game.Tests/Visual/Online/TestSceneScoresContainer.cs index 33f4d577bd..664eca6e23 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneScoresContainer.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneScoresContainer.cs @@ -8,11 +8,13 @@ using System.Collections.Generic; using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; +using osu.Framework.Extensions.LocalisationExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Testing; using osu.Framework.Utils; +using osu.Game.Graphics.Sprites; using osu.Game.Online.API; using osu.Game.Online.API.Requests.Responses; using osu.Game.Overlays; @@ -90,6 +92,43 @@ namespace osu.Game.Tests.Visual.Online AddAssert("no best score displayed", () => scoresContainer.ChildrenOfType().Count() == 1); } + [Test] + public void TestHitResultsWithSameNameAreGrouped() + { + AddStep("Load scores without user best", () => + { + var allScores = createScores(); + allScores.UserScore = null; + scoresContainer.Scores = allScores; + }); + + AddUntilStep("wait for scores displayed", () => scoresContainer.ChildrenOfType().Any()); + AddAssert("only one column for slider end", () => scoresContainer.ScoreTable.Columns.Count(c => c.Header.Equals("slider end")) == 1); + + AddAssert("all rows show non-zero slider ends", () => + { + int sliderEndColumnIndex = Array.FindIndex(scoresContainer.ScoreTable.Columns, c => c != null && c.Header.Equals("slider end")); + bool sliderEndFilledInEachRow = true; + + for (int i = 0; i < scoresContainer.ScoreTable.Content?.GetLength(0); i++) + { + switch (scoresContainer.ScoreTable.Content[i, sliderEndColumnIndex]) + { + case OsuSpriteText text: + if (text.Text.Equals(0.0d.ToLocalisableString(@"N0"))) + sliderEndFilledInEachRow = false; + break; + + default: + sliderEndFilledInEachRow = false; + break; + } + } + + return sliderEndFilledInEachRow; + }); + } + [Test] public void TestUserBest() { @@ -287,13 +326,17 @@ namespace osu.Game.Tests.Visual.Online const int initial_great_count = 2000; const int initial_tick_count = 100; + const int initial_slider_end_count = 500; int greatCount = initial_great_count; int tickCount = initial_tick_count; + int sliderEndCount = initial_slider_end_count; - foreach (var s in scores.Scores) + foreach (var (score, index) in scores.Scores.Select((s, i) => (s, i))) { - s.Statistics = new Dictionary + HitResult sliderEndResult = index % 2 == 0 ? HitResult.SliderTailHit : HitResult.SmallTickHit; + + score.Statistics = new Dictionary { { HitResult.Great, greatCount }, { HitResult.LargeTickHit, tickCount }, @@ -301,10 +344,19 @@ namespace osu.Game.Tests.Visual.Online { HitResult.Meh, RNG.Next(100) }, { HitResult.Miss, initial_great_count - greatCount }, { HitResult.LargeTickMiss, initial_tick_count - tickCount }, + { sliderEndResult, sliderEndCount }, + }; + + // Some hit results, including SliderTailHit and SmallTickHit, are only displayed + // when the maximum number is known + score.MaximumStatistics = new Dictionary + { + { sliderEndResult, initial_slider_end_count }, }; greatCount -= 100; tickCount -= RNG.Next(1, 5); + sliderEndCount -= 20; } return scores; diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs index a6868efb5d..671b5df33b 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs @@ -9,7 +9,6 @@ using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Extensions; -using osu.Framework.Extensions.EnumExtensions; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; @@ -58,9 +57,11 @@ namespace osu.Game.Overlays.BeatmapSet.Scores } /// - /// The statistics that appear in the table, in order of appearance. + /// The statistics that appear in the table, grouped by their display name. If multiple HitResults have the same + /// DisplayName (for example, "slider end" is the name for both HitResult.SliderTailTick and HitResult.SmallTickHit + /// in osu!std) the name will only be listed once. /// - private readonly List<(HitResult result, LocalisableString displayName)> statisticResultTypes = new List<(HitResult, LocalisableString)>(); + private readonly List<(LocalisableString displayName, IEnumerable results)> statisticResults = new List<(LocalisableString displayName, IEnumerable results)>(); private bool showPerformancePoints; @@ -72,7 +73,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores return; showPerformancePoints = showPerformanceColumn; - statisticResultTypes.Clear(); + statisticResults.Clear(); for (int i = 0; i < scores.Count; i++) backgroundFlow.Add(new ScoreTableRowBackground(i, scores[i], row_height)); @@ -105,20 +106,18 @@ namespace osu.Game.Overlays.BeatmapSet.Scores var ruleset = scores.First().Ruleset.CreateInstance(); - foreach (var result in EnumExtensions.GetValuesInOrder()) + foreach (var resultGroup in ruleset.GetHitResults().GroupBy(r => r.displayName)) { - if (!allScoreStatistics.Contains(result)) + if (!resultGroup.Any(r => allScoreStatistics.Contains(r.result))) continue; // for the time being ignore bonus result types. // this is not being sent from the API and will be empty in all cases. - if (result.IsBonus()) + if (resultGroup.All(r => r.result.IsBonus())) continue; - var displayName = ruleset.GetDisplayNameForHitResult(result); - - columns.Add(new TableColumn(displayName, Anchor.CentreLeft, new Dimension(minSize: 35, maxSize: 60))); - statisticResultTypes.Add((result, displayName)); + columns.Add(new TableColumn(resultGroup.Key, Anchor.CentreLeft, new Dimension(minSize: 35, maxSize: 60))); + statisticResults.Add((resultGroup.Key, resultGroup.Select(r => r.result))); } if (showPerformancePoints) @@ -167,12 +166,17 @@ namespace osu.Game.Overlays.BeatmapSet.Scores #pragma warning restore 618 }; - var availableStatistics = score.GetStatisticsForDisplay().ToDictionary(tuple => tuple.Result); + var availableStatistics = score.GetStatisticsForDisplay().ToLookup(touple => touple.DisplayName); - foreach (var result in statisticResultTypes) + foreach (var (columnName, resultTypes) in statisticResults) { - if (!availableStatistics.TryGetValue(result.result, out var stat)) - stat = new HitResultDisplayStatistic(result.result, 0, null, result.displayName); + HitResultDisplayStatistic stat = new HitResultDisplayStatistic(resultTypes.First(), 0, null, columnName); + + if (availableStatistics.Contains(columnName)) + { + foreach (var s in availableStatistics[columnName]) + stat = s; + } content.Add(new StatisticText(stat.Count, stat.MaxCount, @"N0") { Colour = stat.Count == 0 ? Color4.Gray : Color4.White }); } diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs index b53b7826f3..39491e338e 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs @@ -34,7 +34,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores private readonly IBindable user = new Bindable(); private readonly Box background; - private readonly ScoreTable scoreTable; + public readonly ScoreTable ScoreTable; private readonly FillFlowContainer topScoresContainer; private readonly LoadingLayer loading; private readonly LeaderboardModSelector modSelector; @@ -59,8 +59,8 @@ namespace osu.Game.Overlays.BeatmapSet.Scores loadCancellationSource = new CancellationTokenSource(); topScoresContainer.Clear(); - scoreTable.ClearScores(); - scoreTable.Hide(); + ScoreTable.ClearScores(); + ScoreTable.Hide(); loading.Hide(); loading.FinishTransforms(); @@ -85,8 +85,8 @@ namespace osu.Game.Overlays.BeatmapSet.Scores var scores = value.Scores.Select(s => s.ToScoreInfo(rulesets, beatmapInfo)).OrderByTotalScore().ToArray(); var topScore = scores.First(); - scoreTable.DisplayScores(scores, apiBeatmap.Status.GrantsPerformancePoints()); - scoreTable.Show(); + ScoreTable.DisplayScores(scores, apiBeatmap.Status.GrantsPerformancePoints()); + ScoreTable.Show(); var userScore = value.UserScore; var userScoreInfo = userScore?.Score.ToScoreInfo(rulesets, beatmapInfo); @@ -175,7 +175,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores Direction = FillDirection.Vertical, Spacing = new Vector2(0, 5), }, - scoreTable = new ScoreTable + ScoreTable = new ScoreTable { Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, From 868a7db9e9a0b0a49037d234d7f318c793bdc764 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 13 Oct 2024 22:29:00 +0900 Subject: [PATCH 31/75] Start preparing player earlier when quick retrying Should help with https://github.com/ppy/osu/issues/9039. --- osu.Game/Screens/Play/PlayerLoader.cs | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs index 7682bba9a6..9b4bf7d633 100644 --- a/osu.Game/Screens/Play/PlayerLoader.cs +++ b/osu.Game/Screens/Play/PlayerLoader.cs @@ -455,7 +455,19 @@ namespace osu.Game.Screens.Play MetadataInfo.Loading = true; content.FadeInFromZero(500, Easing.OutQuint); - content.ScaleTo(1, 650, Easing.OutQuint).Then().Schedule(prepareNewPlayer); + + if (quickRestart) + { + prepareNewPlayer(); + content.ScaleTo(1, 650, Easing.OutQuint); + } + else + { + content + .ScaleTo(1, 650, Easing.OutQuint) + .Then() + .Schedule(prepareNewPlayer); + } using (BeginDelayedSequence(delayBeforeSideDisplays)) { From 53671ad11eed29e89ad6bff674cce505f09a43ed Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 14 Oct 2024 00:35:18 +0900 Subject: [PATCH 32/75] Only update beatmaps which actually had offsets Without this every beatmap gets a write and it reloads the whole of song select, basically. --- osu.Game/Beatmaps/BeatmapManager.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index f72741af16..4191771116 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -285,7 +285,8 @@ namespace osu.Game.Beatmaps /// /// The query. /// The first result for the provided query, or null if no results were found. - public BeatmapInfo? QueryBeatmap(Expression> query) => Realm.Run(r => r.All().Filter($"{nameof(BeatmapInfo.BeatmapSet)}.{nameof(BeatmapSetInfo.DeletePending)} == false").FirstOrDefault(query)?.Detach()); + public BeatmapInfo? QueryBeatmap(Expression> query) => Realm.Run(r => + r.All().Filter($"{nameof(BeatmapInfo.BeatmapSet)}.{nameof(BeatmapSetInfo.DeletePending)} == false").FirstOrDefault(query)?.Detach()); /// /// A default representation of a WorkingBeatmap to use when no beatmap is available. @@ -322,7 +323,8 @@ namespace osu.Game.Beatmaps foreach (var beatmap in items) { - beatmap.UserSettings.Offset = 0.0; + if (beatmap.UserSettings.Offset != 0) + beatmap.UserSettings.Offset = 0; } PostNotification?.Invoke(new ProgressCompletionNotification { Text = reset_complete_message }); From 8f1fbb44c4e40b57c91d7b4a045e3c3a786dc9e7 Mon Sep 17 00:00:00 2001 From: Leander Furumo Date: Sun, 13 Oct 2024 17:36:06 +0200 Subject: [PATCH 33/75] change fps toggle keybind --- osu.Game/Input/Bindings/GlobalActionContainer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Input/Bindings/GlobalActionContainer.cs b/osu.Game/Input/Bindings/GlobalActionContainer.cs index aca0984e0f..3279eecddc 100644 --- a/osu.Game/Input/Bindings/GlobalActionContainer.cs +++ b/osu.Game/Input/Bindings/GlobalActionContainer.cs @@ -101,7 +101,7 @@ namespace osu.Game.Input.Bindings new KeyBinding(new[] { InputKey.Alt, InputKey.Home }, GlobalAction.Home), - new KeyBinding(new[] { InputKey.Control, InputKey.Shift, InputKey.F }, GlobalAction.ToggleFPSDisplay), + new KeyBinding(new[] { InputKey.Control, InputKey.Alt, InputKey.C }, GlobalAction.ToggleFPSDisplay), new KeyBinding(new[] { InputKey.Control, InputKey.T }, GlobalAction.ToggleToolbar), new KeyBinding(new[] { InputKey.Control, InputKey.Shift, InputKey.S }, GlobalAction.ToggleSkinEditor), From 0e768cc5175000f689ca303e7bbeac1bae767634 Mon Sep 17 00:00:00 2001 From: Leander Furumo Date: Sun, 13 Oct 2024 22:50:58 +0200 Subject: [PATCH 34/75] remove default keybind for toggling fps counter --- osu.Game/Input/Bindings/GlobalActionContainer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Input/Bindings/GlobalActionContainer.cs b/osu.Game/Input/Bindings/GlobalActionContainer.cs index 3279eecddc..0878813982 100644 --- a/osu.Game/Input/Bindings/GlobalActionContainer.cs +++ b/osu.Game/Input/Bindings/GlobalActionContainer.cs @@ -101,7 +101,7 @@ namespace osu.Game.Input.Bindings new KeyBinding(new[] { InputKey.Alt, InputKey.Home }, GlobalAction.Home), - new KeyBinding(new[] { InputKey.Control, InputKey.Alt, InputKey.C }, GlobalAction.ToggleFPSDisplay), + new KeyBinding(new[] { InputKey.None }, GlobalAction.ToggleFPSDisplay), new KeyBinding(new[] { InputKey.Control, InputKey.T }, GlobalAction.ToggleToolbar), new KeyBinding(new[] { InputKey.Control, InputKey.Shift, InputKey.S }, GlobalAction.ToggleSkinEditor), From 379794c46288189ea0dbb7782b72eaf232adc1ba Mon Sep 17 00:00:00 2001 From: Leander Furumo Date: Mon, 14 Oct 2024 00:00:45 +0200 Subject: [PATCH 35/75] replace empty keybinding array with input key type of None --- osu.Game/Input/Bindings/GlobalActionContainer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Input/Bindings/GlobalActionContainer.cs b/osu.Game/Input/Bindings/GlobalActionContainer.cs index 0878813982..3f5fc0ed58 100644 --- a/osu.Game/Input/Bindings/GlobalActionContainer.cs +++ b/osu.Game/Input/Bindings/GlobalActionContainer.cs @@ -101,7 +101,7 @@ namespace osu.Game.Input.Bindings new KeyBinding(new[] { InputKey.Alt, InputKey.Home }, GlobalAction.Home), - new KeyBinding(new[] { InputKey.None }, GlobalAction.ToggleFPSDisplay), + new KeyBinding(InputKey.None, GlobalAction.ToggleFPSDisplay), new KeyBinding(new[] { InputKey.Control, InputKey.T }, GlobalAction.ToggleToolbar), new KeyBinding(new[] { InputKey.Control, InputKey.Shift, InputKey.S }, GlobalAction.ToggleSkinEditor), From 90a08b8a6872585ecf5303fcb5e3e2defc5c1ee9 Mon Sep 17 00:00:00 2001 From: Shin Morisawa Date: Mon, 14 Oct 2024 09:55:28 +0900 Subject: [PATCH 36/75] Fix Beatmap Delete Dialog --- osu.Game/Screens/Select/BeatmapDeleteDialog.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/BeatmapDeleteDialog.cs b/osu.Game/Screens/Select/BeatmapDeleteDialog.cs index 8bc40dbd9e..16f0cbe65e 100644 --- a/osu.Game/Screens/Select/BeatmapDeleteDialog.cs +++ b/osu.Game/Screens/Select/BeatmapDeleteDialog.cs @@ -14,7 +14,7 @@ namespace osu.Game.Screens.Select public BeatmapDeleteDialog(BeatmapSetInfo beatmapSet) { this.beatmapSet = beatmapSet; - BodyText = $@"{beatmapSet.Metadata.Artist} - {beatmapSet.Metadata.Title}"; + BodyText = beatmapSet.Metadata.GetDisplayTitleRomanisable(false); } [BackgroundDependencyLoader] From 17aed26f854ee5c45de5e84bb2ea5639446e708a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 14 Oct 2024 13:03:58 +0200 Subject: [PATCH 37/75] Fix shuffle not actually changing the track sometimes See https://github.com/ppy/osu/pull/30215#issuecomment-2407775408 for context. Turns out the test failures were more correct than I'd thought. The long-and-short of it is that both in "pure random" mode and in "permutation" mode, when running out of track history to fall back on, it was possible for the random algorithm to pick the same song twice in a row - which is probably not desired, and which this explicit exclude should make impossible. --- osu.Game/Overlays/MusicController.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/MusicController.cs b/osu.Game/Overlays/MusicController.cs index a269dbdf4f..0f88765593 100644 --- a/osu.Game/Overlays/MusicController.cs +++ b/osu.Game/Overlays/MusicController.cs @@ -376,7 +376,7 @@ namespace osu.Game.Overlays { Live result; - var possibleSets = getBeatmapSets().Where(s => !s.Value.Protected || allowProtectedTracks).ToArray(); + var possibleSets = getBeatmapSets().Where(s => !s.Value.Equals(current?.BeatmapSetInfo) && (!s.Value.Protected || allowProtectedTracks)).ToArray(); if (possibleSets.Length == 0) return null; From 945d907a3d493c9b399a1b2855a5c67059b0f9f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 14 Oct 2024 14:05:13 +0200 Subject: [PATCH 38/75] Add test covering correct operation of mirror mod --- .../Mods/TestSceneOsuModMirror.cs | 64 +++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModMirror.cs diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModMirror.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModMirror.cs new file mode 100644 index 0000000000..0b3496ba68 --- /dev/null +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModMirror.cs @@ -0,0 +1,64 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Linq; +using NUnit.Framework; +using osu.Framework.Testing; +using osu.Framework.Utils; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Osu.Beatmaps; +using osu.Game.Rulesets.Osu.Mods; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Osu.Objects.Drawables; +using osu.Game.Rulesets.Osu.UI; +using osuTK; + +namespace osu.Game.Rulesets.Osu.Tests.Mods +{ + public partial class TestSceneOsuModMirror : OsuModTestScene + { + [Test] + public void TestCorrectReflections([Values] OsuModMirror.MirrorType type, [Values] bool withStrictTracking) => CreateModTest(new ModTestData + { + Autoplay = true, + Beatmap = new OsuBeatmap + { + HitObjects = + { + new Slider + { + Position = new Vector2(0), + Path = new SliderPath + { + ControlPoints = + { + new PathControlPoint(), + new PathControlPoint(new Vector2(100, 0)) + } + }, + TickDistanceMultiplier = 0.5, + RepeatCount = 1, + } + } + }, + Mods = withStrictTracking + ? [new OsuModMirror { Reflection = { Value = type } }, new OsuModStrictTracking()] + : [new OsuModMirror { Reflection = { Value = type } }], + PassCondition = () => + { + var slider = this.ChildrenOfType().SingleOrDefault(); + var playfield = this.ChildrenOfType().Single(); + + if (slider == null) + return false; + + return Precision.AlmostEquals(playfield.ToLocalSpace(slider.HeadCircle.ScreenSpaceDrawQuad.Centre), slider.HitObject.Position) + && Precision.AlmostEquals(playfield.ToLocalSpace(slider.TailCircle.ScreenSpaceDrawQuad.Centre), slider.HitObject.Position) + && Precision.AlmostEquals(playfield.ToLocalSpace(slider.NestedHitObjects.OfType().Single().ScreenSpaceDrawQuad.Centre), + slider.HitObject.Position + slider.HitObject.Path.PositionAt(1)) + && Precision.AlmostEquals(playfield.ToLocalSpace(slider.NestedHitObjects.OfType().First().ScreenSpaceDrawQuad.Centre), + slider.HitObject.Position + slider.HitObject.Path.PositionAt(0.7f)); + } + }); + } +} From 275b86cd3c92e2d4c0ac9efbd935cb3092cc5ae6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 14 Oct 2024 14:22:26 +0200 Subject: [PATCH 39/75] Fix strict tracking mod not populating path progress for ticks/repeats Closes https://github.com/ppy/osu/issues/30237. This is the root failure in the issue, and one that *only* presents when another conversion mod that repositions the objects is also active. That makes the `PathProgress` of the nesteds to be zero, therefore making them occupy the position of the slider head after any mutation to the path. --- osu.Game.Rulesets.Osu/Mods/OsuModStrictTracking.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModStrictTracking.cs b/osu.Game.Rulesets.Osu/Mods/OsuModStrictTracking.cs index 2c9292c58b..7d2fd628f6 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModStrictTracking.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModStrictTracking.cs @@ -120,6 +120,7 @@ namespace osu.Game.Rulesets.Osu.Mods Position = Position + Path.PositionAt(e.PathProgress), StackHeight = StackHeight, Scale = Scale, + PathProgress = e.PathProgress, }); break; @@ -150,6 +151,7 @@ namespace osu.Game.Rulesets.Osu.Mods Position = Position + Path.PositionAt(e.PathProgress), StackHeight = StackHeight, Scale = Scale, + PathProgress = e.PathProgress, }); break; } From 1f1a174c5068f61f2084577e0c7fd31ae739d2fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 14 Oct 2024 14:06:09 +0200 Subject: [PATCH 40/75] Remove no longer required nested object reposition hacks As touched on in https://github.com/ppy/osu/issues/30237#issuecomment-2408557766, these types of maneouvers are no longer required after https://github.com/ppy/osu/pull/30021 - although as it turns out on closer inspection, these things being there still *did not actually break anything*, because the `slider.Path` mutation at the end of `modifySlider()` causes `updateNestedPositions()` to be called eventually anyway. So this is at mostly a code quality upgrade. --- .../Utils/OsuHitObjectGenerationUtils.cs | 19 +++++-------------- 1 file changed, 5 insertions(+), 14 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils.cs b/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils.cs index e936c24c08..f27624a633 100644 --- a/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils.cs +++ b/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils.cs @@ -3,7 +3,6 @@ using System; using System.Linq; -using osu.Framework.Extensions.IEnumerableExtensions; using osu.Game.Rulesets.Osu.UI; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Osu.Beatmaps; @@ -117,10 +116,9 @@ namespace osu.Game.Rulesets.Osu.Utils if (osuObject is not Slider slider) return; - void reflectNestedObject(OsuHitObject nested) => nested.Position = new Vector2(OsuPlayfield.BASE_SIZE.X - nested.Position.X, nested.Position.Y); static void reflectControlPoint(PathControlPoint point) => point.Position = new Vector2(-point.Position.X, point.Position.Y); - modifySlider(slider, reflectNestedObject, reflectControlPoint); + modifySlider(slider, reflectControlPoint); } /// @@ -134,10 +132,9 @@ namespace osu.Game.Rulesets.Osu.Utils if (osuObject is not Slider slider) return; - void reflectNestedObject(OsuHitObject nested) => nested.Position = new Vector2(nested.Position.X, OsuPlayfield.BASE_SIZE.Y - nested.Position.Y); static void reflectControlPoint(PathControlPoint point) => point.Position = new Vector2(point.Position.X, -point.Position.Y); - modifySlider(slider, reflectNestedObject, reflectControlPoint); + modifySlider(slider, reflectControlPoint); } /// @@ -146,10 +143,9 @@ namespace osu.Game.Rulesets.Osu.Utils /// The slider to be flipped. public static void FlipSliderInPlaceHorizontally(Slider slider) { - void flipNestedObject(OsuHitObject nested) => nested.Position = new Vector2(slider.X - (nested.X - slider.X), nested.Y); static void flipControlPoint(PathControlPoint point) => point.Position = new Vector2(-point.Position.X, point.Position.Y); - modifySlider(slider, flipNestedObject, flipControlPoint); + modifySlider(slider, flipControlPoint); } /// @@ -159,18 +155,13 @@ namespace osu.Game.Rulesets.Osu.Utils /// The angle, measured in radians, to rotate the slider by. public static void RotateSlider(Slider slider, float rotation) { - void rotateNestedObject(OsuHitObject nested) => nested.Position = rotateVector(nested.Position - slider.Position, rotation) + slider.Position; void rotateControlPoint(PathControlPoint point) => point.Position = rotateVector(point.Position, rotation); - modifySlider(slider, rotateNestedObject, rotateControlPoint); + modifySlider(slider, rotateControlPoint); } - private static void modifySlider(Slider slider, Action modifyNestedObject, Action modifyControlPoint) + private static void modifySlider(Slider slider, Action modifyControlPoint) { - // No need to update the head and tail circles, since slider handles that when the new slider path is set - slider.NestedHitObjects.OfType().ForEach(modifyNestedObject); - slider.NestedHitObjects.OfType().ForEach(modifyNestedObject); - var controlPoints = slider.Path.ControlPoints.Select(p => new PathControlPoint(p.Position, p.Type)).ToArray(); foreach (var point in controlPoints) modifyControlPoint(point); From 8cec318c1fc2ec9d6eafd0b4a00d8320ca2c5123 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 14 Oct 2024 15:28:32 +0200 Subject: [PATCH 41/75] Loop track even if shuffling if there is only one available Co-authored-by: Dean Herbert --- osu.Game/Overlays/MusicController.cs | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/osu.Game/Overlays/MusicController.cs b/osu.Game/Overlays/MusicController.cs index 0f88765593..87920fdf55 100644 --- a/osu.Game/Overlays/MusicController.cs +++ b/osu.Game/Overlays/MusicController.cs @@ -376,11 +376,19 @@ namespace osu.Game.Overlays { Live result; - var possibleSets = getBeatmapSets().Where(s => !s.Value.Equals(current?.BeatmapSetInfo) && (!s.Value.Protected || allowProtectedTracks)).ToArray(); + var possibleSets = getBeatmapSets().Where(s => !s.Value.Protected || allowProtectedTracks).ToList(); - if (possibleSets.Length == 0) + if (possibleSets.Count == 0) return null; + // if there is only one possible set left, play it, even if it is the same as the current track. + // looping is preferable over playing nothing. + if (possibleSets.Count == 1) + return possibleSets.Single(); + + // now that we actually know there is a choice, do not allow the current track to be played again. + possibleSets.RemoveAll(s => s.Value.Equals(current?.BeatmapSetInfo)); + // condition below checks if the signs of `randomHistoryDirection` and `direction` are opposite and not zero. // if that is the case, it means that the user had previously chosen next track `randomHistoryDirection` times and wants to go back, // or that the user had previously chosen previous track `randomHistoryDirection` times and wants to go forward. @@ -410,20 +418,20 @@ namespace osu.Game.Overlays switch (randomSelectAlgorithm.Value) { case RandomSelectAlgorithm.Random: - result = possibleSets[RNG.Next(possibleSets.Length)]; + result = possibleSets[RNG.Next(possibleSets.Count)]; break; case RandomSelectAlgorithm.RandomPermutation: - var notYetPlayedSets = possibleSets.Except(previousRandomSets).ToArray(); + var notYetPlayedSets = possibleSets.Except(previousRandomSets).ToList(); - if (notYetPlayedSets.Length == 0) + if (notYetPlayedSets.Count == 0) { notYetPlayedSets = possibleSets; previousRandomSets.Clear(); randomHistoryDirection = 0; } - result = notYetPlayedSets[RNG.Next(notYetPlayedSets.Length)]; + result = notYetPlayedSets[RNG.Next(notYetPlayedSets.Count)]; break; default: From 035e5a961380dc992472d0e245807bad3f955ac8 Mon Sep 17 00:00:00 2001 From: Leander Furumo Date: Mon, 14 Oct 2024 16:03:29 +0200 Subject: [PATCH 42/75] migrate clearance of conflicting ToggleFPSDisplay keybind --- osu.Game/Database/RealmAccess.cs | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/osu.Game/Database/RealmAccess.cs b/osu.Game/Database/RealmAccess.cs index ad0423191d..2f25791964 100644 --- a/osu.Game/Database/RealmAccess.cs +++ b/osu.Game/Database/RealmAccess.cs @@ -93,8 +93,9 @@ namespace osu.Game.Database /// 40 2023-12-21 Add ScoreInfo.Version to keep track of which build scores were set on. /// 41 2024-04-17 Add ScoreInfo.TotalScoreWithoutMods for future mod multiplier rebalances. /// 42 2024-08-07 Update mania key bindings to reflect changes to ManiaAction + /// 43 2024-10-14 Reset keybind for toggling FPS display to avoid conflict with "convert to stream" in the editor, if not already changed by user. /// - private const int schema_version = 42; + private const int schema_version = 43; /// /// Lock object which is held during sections, blocking realm retrieval during blocking periods. @@ -1192,6 +1193,21 @@ namespace osu.Game.Database } break; + + case 43: + { + // Clear default bindings for "Toggle FPS Display", + // as it conflicts with "Convert to Stream" in the editor. + // Only apply change if set to the conflicting bind + // i.e. has been manually rebound by the user. + var keyBindings = migration.NewRealm.All(); + + var toggleFpsBind = keyBindings.FirstOrDefault(bind => bind.ActionInt == (int)GlobalAction.ToggleFPSDisplay); + if (toggleFpsBind != null && toggleFpsBind.KeyCombination.Keys.SequenceEqual(new[] { InputKey.Control, InputKey.Shift, InputKey.F })) + migration.NewRealm.Remove(toggleFpsBind); + + break; + } } Logger.Log($"Migration completed in {stopwatch.ElapsedMilliseconds}ms"); From 750e0b29cae455f89c46f9f0c033d9861dc84a2d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20Brandst=C3=B6tter?= Date: Mon, 14 Oct 2024 16:11:58 +0200 Subject: [PATCH 43/75] Use `ChildrenOfType<>` to get ScoreTable to test --- .../Visual/Online/TestSceneScoresContainer.cs | 13 +++++++++---- .../Overlays/BeatmapSet/Scores/ScoresContainer.cs | 12 ++++++------ 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneScoresContainer.cs b/osu.Game.Tests/Visual/Online/TestSceneScoresContainer.cs index 664eca6e23..c17d746232 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneScoresContainer.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneScoresContainer.cs @@ -103,16 +103,21 @@ namespace osu.Game.Tests.Visual.Online }); AddUntilStep("wait for scores displayed", () => scoresContainer.ChildrenOfType().Any()); - AddAssert("only one column for slider end", () => scoresContainer.ScoreTable.Columns.Count(c => c.Header.Equals("slider end")) == 1); + AddAssert("only one column for slider end", () => + { + ScoreTable scoreTable = scoresContainer.ChildrenOfType().First(); + return scoreTable.Columns.Count(c => c.Header.Equals("slider end")) == 1; + }); AddAssert("all rows show non-zero slider ends", () => { - int sliderEndColumnIndex = Array.FindIndex(scoresContainer.ScoreTable.Columns, c => c != null && c.Header.Equals("slider end")); + ScoreTable scoreTable = scoresContainer.ChildrenOfType().First(); + int sliderEndColumnIndex = Array.FindIndex(scoreTable.Columns, c => c != null && c.Header.Equals("slider end")); bool sliderEndFilledInEachRow = true; - for (int i = 0; i < scoresContainer.ScoreTable.Content?.GetLength(0); i++) + for (int i = 0; i < scoreTable.Content?.GetLength(0); i++) { - switch (scoresContainer.ScoreTable.Content[i, sliderEndColumnIndex]) + switch (scoreTable.Content[i, sliderEndColumnIndex]) { case OsuSpriteText text: if (text.Text.Equals(0.0d.ToLocalisableString(@"N0"))) diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs index 39491e338e..b53b7826f3 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs @@ -34,7 +34,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores private readonly IBindable user = new Bindable(); private readonly Box background; - public readonly ScoreTable ScoreTable; + private readonly ScoreTable scoreTable; private readonly FillFlowContainer topScoresContainer; private readonly LoadingLayer loading; private readonly LeaderboardModSelector modSelector; @@ -59,8 +59,8 @@ namespace osu.Game.Overlays.BeatmapSet.Scores loadCancellationSource = new CancellationTokenSource(); topScoresContainer.Clear(); - ScoreTable.ClearScores(); - ScoreTable.Hide(); + scoreTable.ClearScores(); + scoreTable.Hide(); loading.Hide(); loading.FinishTransforms(); @@ -85,8 +85,8 @@ namespace osu.Game.Overlays.BeatmapSet.Scores var scores = value.Scores.Select(s => s.ToScoreInfo(rulesets, beatmapInfo)).OrderByTotalScore().ToArray(); var topScore = scores.First(); - ScoreTable.DisplayScores(scores, apiBeatmap.Status.GrantsPerformancePoints()); - ScoreTable.Show(); + scoreTable.DisplayScores(scores, apiBeatmap.Status.GrantsPerformancePoints()); + scoreTable.Show(); var userScore = value.UserScore; var userScoreInfo = userScore?.Score.ToScoreInfo(rulesets, beatmapInfo); @@ -175,7 +175,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores Direction = FillDirection.Vertical, Spacing = new Vector2(0, 5), }, - ScoreTable = new ScoreTable + scoreTable = new ScoreTable { Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, From d7021f989b61e98a0acdf5cbf2c3085bf9c47d11 Mon Sep 17 00:00:00 2001 From: schiavoanto Date: Mon, 14 Oct 2024 16:14:23 +0200 Subject: [PATCH 44/75] Revert 9cd7f2b and fc1ebfd --- osu.Game/Localisation/SkinEditorStrings.cs | 10 ---------- osu.Game/Skinning/GlobalSkinnableContainerLookup.cs | 4 ++-- osu.Game/Skinning/GlobalSkinnableContainers.cs | 9 ++++----- 3 files changed, 6 insertions(+), 17 deletions(-) diff --git a/osu.Game/Localisation/SkinEditorStrings.cs b/osu.Game/Localisation/SkinEditorStrings.cs index 9ae9aa0747..d96ea7dd9f 100644 --- a/osu.Game/Localisation/SkinEditorStrings.cs +++ b/osu.Game/Localisation/SkinEditorStrings.cs @@ -34,16 +34,6 @@ namespace osu.Game.Localisation /// public static LocalisableString Gameplay => new TranslatableString(getKey(@"gameplay"), @"Gameplay"); - /// - /// "HUD" - /// - public static LocalisableString HUD => new TranslatableString(getKey(@"hud"), @"HUD"); - - /// - /// "Playfield" - /// - public static LocalisableString Playfield => new TranslatableString(getKey(@"playfield"), @"Playfield"); - /// /// "Settings ({0})" /// diff --git a/osu.Game/Skinning/GlobalSkinnableContainerLookup.cs b/osu.Game/Skinning/GlobalSkinnableContainerLookup.cs index 33b5527917..6d78981f0a 100644 --- a/osu.Game/Skinning/GlobalSkinnableContainerLookup.cs +++ b/osu.Game/Skinning/GlobalSkinnableContainerLookup.cs @@ -31,9 +31,9 @@ namespace osu.Game.Skinning public override string ToString() { - if (Ruleset == null) return Lookup.GetLocalisableDescription().ToString(); + if (Ruleset == null) return Lookup.GetDescription(); - return $"{Lookup.GetLocalisableDescription().ToString()} (\"{Ruleset.Name}\" only)"; + return $"{Lookup.GetDescription()} (\"{Ruleset.Name}\" only)"; } public bool Equals(GlobalSkinnableContainerLookup? other) diff --git a/osu.Game/Skinning/GlobalSkinnableContainers.cs b/osu.Game/Skinning/GlobalSkinnableContainers.cs index 73cb1303a0..02f915895f 100644 --- a/osu.Game/Skinning/GlobalSkinnableContainers.cs +++ b/osu.Game/Skinning/GlobalSkinnableContainers.cs @@ -1,8 +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.Localisation; -using osu.Game.Localisation; +using System.ComponentModel; namespace osu.Game.Skinning { @@ -11,13 +10,13 @@ namespace osu.Game.Skinning /// public enum GlobalSkinnableContainers { - [LocalisableDescription(typeof(SkinEditorStrings), nameof(SkinEditorStrings.HUD))] + [Description("HUD")] MainHUDComponents, - [LocalisableDescription(typeof(SkinEditorStrings), nameof(SkinEditorStrings.SongSelect))] + [Description("Song select")] SongSelect, - [LocalisableDescription(typeof(SkinEditorStrings), nameof(SkinEditorStrings.Playfield))] + [Description("Playfield")] Playfield } } From 25c0ff4168a6e116faa3e7814a5d75959f9442ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20Brandst=C3=B6tter?= Date: Mon, 14 Oct 2024 16:14:29 +0200 Subject: [PATCH 45/75] Correct reference to hit result and link to them --- osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs index 671b5df33b..e6052b0e3b 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs @@ -58,8 +58,8 @@ namespace osu.Game.Overlays.BeatmapSet.Scores /// /// The statistics that appear in the table, grouped by their display name. If multiple HitResults have the same - /// DisplayName (for example, "slider end" is the name for both HitResult.SliderTailTick and HitResult.SmallTickHit - /// in osu!std) the name will only be listed once. + /// DisplayName (for example, "slider end" is the name for both and + /// in osu!) the name will only be listed once. /// private readonly List<(LocalisableString displayName, IEnumerable results)> statisticResults = new List<(LocalisableString displayName, IEnumerable results)>(); From 511f0e99b3933459e28c53456566c0c5639f845c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20Brandst=C3=B6tter?= Date: Mon, 14 Oct 2024 16:16:00 +0200 Subject: [PATCH 46/75] Correct typo --- osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs index e6052b0e3b..4150316f4b 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs @@ -166,7 +166,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores #pragma warning restore 618 }; - var availableStatistics = score.GetStatisticsForDisplay().ToLookup(touple => touple.DisplayName); + var availableStatistics = score.GetStatisticsForDisplay().ToLookup(tuple => tuple.DisplayName); foreach (var (columnName, resultTypes) in statisticResults) { From 285756802cd17710078f9e7576658255c7cced47 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20Brandst=C3=B6tter?= Date: Mon, 14 Oct 2024 16:33:30 +0200 Subject: [PATCH 47/75] Sum up totals for hit results with the same name --- osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs index 4150316f4b..40055bbda8 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs @@ -170,15 +170,20 @@ namespace osu.Game.Overlays.BeatmapSet.Scores foreach (var (columnName, resultTypes) in statisticResults) { - HitResultDisplayStatistic stat = new HitResultDisplayStatistic(resultTypes.First(), 0, null, columnName); + int count = 0; + int? maxCount = null; if (availableStatistics.Contains(columnName)) { + maxCount = 0; foreach (var s in availableStatistics[columnName]) - stat = s; + { + count += s.Count; + maxCount += s.MaxCount; + } } - content.Add(new StatisticText(stat.Count, stat.MaxCount, @"N0") { Colour = stat.Count == 0 ? Color4.Gray : Color4.White }); + content.Add(new StatisticText(count, maxCount, @"N0") { Colour = count == 0 ? Color4.Gray : Color4.White }); } if (showPerformancePoints) From a007a81fe8518215ee5fd40dbfab56a3696cd2eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20Brandst=C3=B6tter?= Date: Mon, 14 Oct 2024 16:55:07 +0200 Subject: [PATCH 48/75] Only keep track of the names of hit results to display in a `ScoreTable` --- osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs index 40055bbda8..c70c41feed 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs @@ -57,11 +57,11 @@ namespace osu.Game.Overlays.BeatmapSet.Scores } /// - /// The statistics that appear in the table, grouped by their display name. If multiple HitResults have the same + /// The names of the statistics that appear in the table. If multiple HitResults have the same /// DisplayName (for example, "slider end" is the name for both and /// in osu!) the name will only be listed once. /// - private readonly List<(LocalisableString displayName, IEnumerable results)> statisticResults = new List<(LocalisableString displayName, IEnumerable results)>(); + private readonly List statisticResultNames = new List(); private bool showPerformancePoints; @@ -73,7 +73,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores return; showPerformancePoints = showPerformanceColumn; - statisticResults.Clear(); + statisticResultNames.Clear(); for (int i = 0; i < scores.Count; i++) backgroundFlow.Add(new ScoreTableRowBackground(i, scores[i], row_height)); @@ -117,7 +117,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores continue; columns.Add(new TableColumn(resultGroup.Key, Anchor.CentreLeft, new Dimension(minSize: 35, maxSize: 60))); - statisticResults.Add((resultGroup.Key, resultGroup.Select(r => r.result))); + statisticResultNames.Add(resultGroup.Key); } if (showPerformancePoints) @@ -168,7 +168,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores var availableStatistics = score.GetStatisticsForDisplay().ToLookup(tuple => tuple.DisplayName); - foreach (var (columnName, resultTypes) in statisticResults) + foreach (var columnName in statisticResultNames) { int count = 0; int? maxCount = null; @@ -176,6 +176,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores if (availableStatistics.Contains(columnName)) { maxCount = 0; + foreach (var s in availableStatistics[columnName]) { count += s.Count; From 634f20e8de5577622d6fe9946ad737d2ec0401ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 15 Oct 2024 14:11:33 +0200 Subject: [PATCH 49/75] Ensure at least scale popover axis toggle is active at any time As in, toggling off an axis if it is the only one enabled will enable the other one in turn. Co-authored-by: Dean Herbert --- .../Edit/PreciseScalePopover.cs | 34 ++++++++++++++----- 1 file changed, 26 insertions(+), 8 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs b/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs index 25515a90d5..71a7793fc9 100644 --- a/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs +++ b/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs @@ -136,8 +136,26 @@ namespace osu.Game.Rulesets.Osu.Edit }); scaleInput.Current.BindValueChanged(scale => scaleInfo.Value = scaleInfo.Value with { Scale = scale.NewValue }); - xCheckBox.Current.BindValueChanged(x => setAxis(x.NewValue, yCheckBox.Current.Value)); - yCheckBox.Current.BindValueChanged(y => setAxis(xCheckBox.Current.Value, y.NewValue)); + xCheckBox.Current.BindValueChanged(_ => + { + if (!xCheckBox.Current.Value && !yCheckBox.Current.Value) + { + yCheckBox.Current.Value = true; + return; + } + + updateAxes(); + }); + yCheckBox.Current.BindValueChanged(_ => + { + if (!xCheckBox.Current.Value && !yCheckBox.Current.Value) + { + xCheckBox.Current.Value = true; + return; + } + + updateAxes(); + }); selectionCentreButton.Selected.Disabled = !(scaleHandler.CanScaleX.Value || scaleHandler.CanScaleY.Value); playfieldCentreButton.Selected.Disabled = scaleHandler.IsScalingSlider.Value && !selectionCentreButton.Selected.Disabled; @@ -152,6 +170,12 @@ namespace osu.Game.Rulesets.Osu.Edit }); } + private void updateAxes() + { + scaleInfo.Value = scaleInfo.Value with { XAxis = xCheckBox.Current.Value, YAxis = yCheckBox.Current.Value }; + updateMaxScale(); + } + private void updateAxisCheckBoxesEnabled() { if (scaleInfo.Value.Origin != ScaleOrigin.SelectionCentre) @@ -234,12 +258,6 @@ namespace osu.Game.Rulesets.Osu.Edit private float getRotation(PreciseScaleInfo scale) => scale.Origin == ScaleOrigin.GridCentre ? gridToolbox.GridLinesRotation.Value : 0; - private void setAxis(bool x, bool y) - { - scaleInfo.Value = scaleInfo.Value with { XAxis = x, YAxis = y }; - updateMaxScale(); - } - protected override void PopIn() { base.PopIn(); From f8a13b0beb6c47a4e354cd2732e51b7171a60c33 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 16 Oct 2024 08:17:22 +0200 Subject: [PATCH 50/75] Fix migration not checking combination properly --- osu.Game/Database/RealmAccess.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Database/RealmAccess.cs b/osu.Game/Database/RealmAccess.cs index 2f25791964..a437b3313e 100644 --- a/osu.Game/Database/RealmAccess.cs +++ b/osu.Game/Database/RealmAccess.cs @@ -1203,7 +1203,7 @@ namespace osu.Game.Database var keyBindings = migration.NewRealm.All(); var toggleFpsBind = keyBindings.FirstOrDefault(bind => bind.ActionInt == (int)GlobalAction.ToggleFPSDisplay); - if (toggleFpsBind != null && toggleFpsBind.KeyCombination.Keys.SequenceEqual(new[] { InputKey.Control, InputKey.Shift, InputKey.F })) + if (toggleFpsBind != null && toggleFpsBind.KeyCombination.Keys.SequenceEqual(new[] { InputKey.Shift, InputKey.Control, InputKey.F })) migration.NewRealm.Remove(toggleFpsBind); break; From 882df6b828810d3551f4432816754412e1c71153 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 16 Oct 2024 09:59:05 +0200 Subject: [PATCH 51/75] Remove unused field Not sure why inspectcode is quiet about this? --- osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs b/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs index 4d1e06c857..768a764ad1 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs @@ -25,8 +25,6 @@ namespace osu.Game.Rulesets.Osu.Edit { public partial class OsuGridToolboxGroup : EditorToolboxGroup, IKeyBindingHandler { - private static readonly PositionSnapGridType[] grid_types = Enum.GetValues(typeof(PositionSnapGridType)).Cast().ToArray(); - [Resolved] private EditorBeatmap editorBeatmap { get; set; } = null!; From b29a17364dce4409c4d2293b8f06e379f43b054b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 16 Oct 2024 20:16:10 +0900 Subject: [PATCH 52/75] Remove hold-to-confirm delay when pausing using keyboard shortcuts --- osu.Game/Screens/Play/HUD/HoldForMenuButton.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/HoldForMenuButton.cs b/osu.Game/Screens/Play/HUD/HoldForMenuButton.cs index 89d083eca9..806985e19d 100644 --- a/osu.Game/Screens/Play/HUD/HoldForMenuButton.cs +++ b/osu.Game/Screens/Play/HUD/HoldForMenuButton.cs @@ -299,7 +299,7 @@ namespace osu.Game.Screens.Play.HUD { case GlobalAction.Back: if (!pendingAnimation) - BeginConfirm(); + Confirm(); return true; case GlobalAction.PauseGameplay: @@ -307,7 +307,7 @@ namespace osu.Game.Screens.Play.HUD if (ReplayLoaded.Value) return false; if (!pendingAnimation) - BeginConfirm(); + Confirm(); return true; } From 135b85a55a21d08bd713ffa291ebd723459771c9 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 17 Oct 2024 13:43:53 +0900 Subject: [PATCH 53/75] Improve diffcalc workflow with explicit wait + logs --- .github/workflows/diffcalc.yml | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/.github/workflows/diffcalc.yml b/.github/workflows/diffcalc.yml index 3b77a463c1..805523de8b 100644 --- a/.github/workflows/diffcalc.yml +++ b/.github/workflows/diffcalc.yml @@ -341,9 +341,12 @@ jobs: sed -i 's/^GH_TOKEN=.*$/GH_TOKEN=${{ github.token }}/' "${{ needs.directory.outputs.GENERATOR_ENV }}" cd "${{ needs.directory.outputs.GENERATOR_DIR }}" - docker-compose up --build generator - link=$(docker-compose logs generator -n 10 | grep 'http' | sed -E 's/^.*(http.*)$/\1/') + docker compose up --build --detach + docker compose logs --follow & + docker compose wait generator + + link=$(docker compose logs generator --tail 10 | grep 'http' | sed -E 's/^.*(http.*)$/\1/') target=$(cat "${{ needs.directory.outputs.GENERATOR_ENV }}" | grep -E '^OSU_B=' | cut -d '=' -f2-) echo "TARGET=${target}" >> "${GITHUB_OUTPUT}" @@ -353,7 +356,7 @@ jobs: if: ${{ always() }} run: | cd "${{ needs.directory.outputs.GENERATOR_DIR }}" - docker-compose down -v + docker compose down --volumes output-cli: name: Output info From 102f55a2133373cfdb0eb5dacb20eb0bcf710ca8 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 17 Oct 2024 16:01:44 +0900 Subject: [PATCH 54/75] Refactor BeatmapAttributeText to compute values on the fly --- .../Components/BeatmapAttributeText.cs | 165 ++++++++++++------ 1 file changed, 111 insertions(+), 54 deletions(-) diff --git a/osu.Game/Skinning/Components/BeatmapAttributeText.cs b/osu.Game/Skinning/Components/BeatmapAttributeText.cs index 6e1d655cef..63ba6d1581 100644 --- a/osu.Game/Skinning/Components/BeatmapAttributeText.cs +++ b/osu.Game/Skinning/Components/BeatmapAttributeText.cs @@ -3,8 +3,6 @@ using System; using System.Collections.Generic; -using System.Collections.Immutable; -using System.Linq; using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -35,25 +33,6 @@ namespace osu.Game.Skinning.Components [Resolved] private IBindable beatmap { get; set; } = null!; - private readonly Dictionary valueDictionary = new Dictionary(); - - private static readonly ImmutableDictionary label_dictionary = new Dictionary - { - [BeatmapAttribute.CircleSize] = BeatmapsetsStrings.ShowStatsCs, - [BeatmapAttribute.Accuracy] = BeatmapsetsStrings.ShowStatsAccuracy, - [BeatmapAttribute.HPDrain] = BeatmapsetsStrings.ShowStatsDrain, - [BeatmapAttribute.ApproachRate] = BeatmapsetsStrings.ShowStatsAr, - [BeatmapAttribute.StarRating] = BeatmapsetsStrings.ShowStatsStars, - [BeatmapAttribute.Title] = EditorSetupStrings.Title, - [BeatmapAttribute.Artist] = EditorSetupStrings.Artist, - [BeatmapAttribute.DifficultyName] = EditorSetupStrings.DifficultyHeader, - [BeatmapAttribute.Creator] = EditorSetupStrings.Creator, - [BeatmapAttribute.Source] = EditorSetupStrings.Source, - [BeatmapAttribute.Length] = ArtistStrings.TracklistLength.ToTitle(), - [BeatmapAttribute.RankedStatus] = BeatmapDiscussionsStrings.IndexFormBeatmapsetStatusDefault, - [BeatmapAttribute.BPM] = BeatmapsetsStrings.ShowStatsBpm, - }.ToImmutableDictionary(); - private readonly OsuSpriteText text; public BeatmapAttributeText() @@ -74,52 +53,130 @@ namespace osu.Game.Skinning.Components { base.LoadComplete(); - Attribute.BindValueChanged(_ => updateLabel()); - Template.BindValueChanged(_ => updateLabel()); - beatmap.BindValueChanged(b => - { - updateBeatmapContent(b.NewValue); - updateLabel(); - }, true); + Attribute.BindValueChanged(_ => updateText()); + Template.BindValueChanged(_ => updateText()); + beatmap.BindValueChanged(b => updateText()); + + updateText(); } - private void updateBeatmapContent(WorkingBeatmap workingBeatmap) - { - valueDictionary[BeatmapAttribute.Title] = new RomanisableString(workingBeatmap.BeatmapInfo.Metadata.TitleUnicode, workingBeatmap.BeatmapInfo.Metadata.Title); - valueDictionary[BeatmapAttribute.Artist] = new RomanisableString(workingBeatmap.BeatmapInfo.Metadata.ArtistUnicode, workingBeatmap.BeatmapInfo.Metadata.Artist); - valueDictionary[BeatmapAttribute.DifficultyName] = workingBeatmap.BeatmapInfo.DifficultyName; - valueDictionary[BeatmapAttribute.Creator] = workingBeatmap.BeatmapInfo.Metadata.Author.Username; - valueDictionary[BeatmapAttribute.Source] = workingBeatmap.BeatmapInfo.Metadata.Source; - valueDictionary[BeatmapAttribute.Length] = TimeSpan.FromMilliseconds(workingBeatmap.BeatmapInfo.Length).ToFormattedDuration(); - valueDictionary[BeatmapAttribute.RankedStatus] = workingBeatmap.BeatmapInfo.Status.GetLocalisableDescription(); - valueDictionary[BeatmapAttribute.BPM] = workingBeatmap.BeatmapInfo.BPM.ToLocalisableString(@"F2"); - valueDictionary[BeatmapAttribute.CircleSize] = ((double)workingBeatmap.BeatmapInfo.Difficulty.CircleSize).ToLocalisableString(@"F2"); - valueDictionary[BeatmapAttribute.HPDrain] = ((double)workingBeatmap.BeatmapInfo.Difficulty.DrainRate).ToLocalisableString(@"F2"); - valueDictionary[BeatmapAttribute.Accuracy] = ((double)workingBeatmap.BeatmapInfo.Difficulty.OverallDifficulty).ToLocalisableString(@"F2"); - valueDictionary[BeatmapAttribute.ApproachRate] = ((double)workingBeatmap.BeatmapInfo.Difficulty.ApproachRate).ToLocalisableString(@"F2"); - valueDictionary[BeatmapAttribute.StarRating] = workingBeatmap.BeatmapInfo.StarRating.ToLocalisableString(@"F2"); - } - - private void updateLabel() + private void updateText() { string numberedTemplate = Template.Value .Replace("{", "{{") .Replace("}", "}}") .Replace(@"{{Label}}", "{0}") - .Replace(@"{{Value}}", $"{{{1 + (int)Attribute.Value}}}"); + .Replace(@"{{Value}}", "{1}"); - object?[] args = valueDictionary.OrderBy(pair => pair.Key) - .Select(pair => pair.Value) - .Prepend(label_dictionary[Attribute.Value]) - .Cast() - .ToArray(); + List values = new List + { + getLabelString(Attribute.Value), + getValueString(Attribute.Value) + }; foreach (var type in Enum.GetValues()) { - numberedTemplate = numberedTemplate.Replace($"{{{{{type}}}}}", $"{{{1 + (int)type}}}"); + numberedTemplate = numberedTemplate.Replace($"{{{{{type}}}}}", $"{{{values.Count}}}"); + values.Add(getValueString(type)); } - text.Text = LocalisableString.Format(numberedTemplate, args); + text.Text = LocalisableString.Format(numberedTemplate, values.ToArray()); + } + + private LocalisableString getLabelString(BeatmapAttribute attribute) + { + switch (attribute) + { + case BeatmapAttribute.CircleSize: + return BeatmapsetsStrings.ShowStatsCs; + + case BeatmapAttribute.Accuracy: + return BeatmapsetsStrings.ShowStatsAccuracy; + + case BeatmapAttribute.HPDrain: + return BeatmapsetsStrings.ShowStatsDrain; + + case BeatmapAttribute.ApproachRate: + return BeatmapsetsStrings.ShowStatsAr; + + case BeatmapAttribute.StarRating: + return BeatmapsetsStrings.ShowStatsStars; + + case BeatmapAttribute.Title: + return EditorSetupStrings.Title; + + case BeatmapAttribute.Artist: + return EditorSetupStrings.Artist; + + case BeatmapAttribute.DifficultyName: + return EditorSetupStrings.DifficultyHeader; + + case BeatmapAttribute.Creator: + return EditorSetupStrings.Creator; + + case BeatmapAttribute.Source: + return EditorSetupStrings.Source; + + case BeatmapAttribute.Length: + return ArtistStrings.TracklistLength.ToTitle(); + + case BeatmapAttribute.RankedStatus: + return BeatmapDiscussionsStrings.IndexFormBeatmapsetStatusDefault; + + case BeatmapAttribute.BPM: + return BeatmapsetsStrings.ShowStatsBpm; + + default: + throw new ArgumentOutOfRangeException(); + } + } + + private LocalisableString getValueString(BeatmapAttribute attribute) + { + switch (attribute) + { + case BeatmapAttribute.Title: + return new RomanisableString(beatmap.Value.BeatmapInfo.Metadata.TitleUnicode, beatmap.Value.BeatmapInfo.Metadata.Title); + + case BeatmapAttribute.Artist: + return new RomanisableString(beatmap.Value.BeatmapInfo.Metadata.ArtistUnicode, beatmap.Value.BeatmapInfo.Metadata.Artist); + + case BeatmapAttribute.DifficultyName: + return beatmap.Value.BeatmapInfo.DifficultyName; + + case BeatmapAttribute.Creator: + return beatmap.Value.BeatmapInfo.Metadata.Author.Username; + + case BeatmapAttribute.Source: + return beatmap.Value.BeatmapInfo.Metadata.Source; + + case BeatmapAttribute.Length: + return TimeSpan.FromMilliseconds(beatmap.Value.BeatmapInfo.Length).ToFormattedDuration(); + + case BeatmapAttribute.RankedStatus: + return beatmap.Value.BeatmapInfo.Status.GetLocalisableDescription(); + + case BeatmapAttribute.BPM: + return beatmap.Value.BeatmapInfo.BPM.ToLocalisableString(@"F2"); + + case BeatmapAttribute.CircleSize: + return ((double)beatmap.Value.BeatmapInfo.Difficulty.CircleSize).ToLocalisableString(@"F2"); + + case BeatmapAttribute.HPDrain: + return ((double)beatmap.Value.BeatmapInfo.Difficulty.DrainRate).ToLocalisableString(@"F2"); + + case BeatmapAttribute.Accuracy: + return ((double)beatmap.Value.BeatmapInfo.Difficulty.OverallDifficulty).ToLocalisableString(@"F2"); + + case BeatmapAttribute.ApproachRate: + return ((double)beatmap.Value.BeatmapInfo.Difficulty.ApproachRate).ToLocalisableString(@"F2"); + + case BeatmapAttribute.StarRating: + return beatmap.Value.BeatmapInfo.StarRating.ToLocalisableString(@"F2"); + + default: + throw new ArgumentOutOfRangeException(); + } } protected override void SetFont(FontUsage font) => text.Font = font.With(size: 40); From 1536a9886c18ec25f974d9d237b1887cd763cbaf Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 17 Oct 2024 16:57:00 +0900 Subject: [PATCH 55/75] Fix argument order in diffcalc workflow --- .github/workflows/diffcalc.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/diffcalc.yml b/.github/workflows/diffcalc.yml index 805523de8b..c2eeff20df 100644 --- a/.github/workflows/diffcalc.yml +++ b/.github/workflows/diffcalc.yml @@ -346,7 +346,7 @@ jobs: docker compose logs --follow & docker compose wait generator - link=$(docker compose logs generator --tail 10 | grep 'http' | sed -E 's/^.*(http.*)$/\1/') + link=$(docker compose logs --tail 10 generator | grep 'http' | sed -E 's/^.*(http.*)$/\1/') target=$(cat "${{ needs.directory.outputs.GENERATOR_ENV }}" | grep -E '^OSU_B=' | cut -d '=' -f2-) echo "TARGET=${target}" >> "${GITHUB_OUTPUT}" From f3178b1fef38ae606b8b632735556fa9dff50b61 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 17 Oct 2024 21:16:45 +0900 Subject: [PATCH 56/75] Add test scene --- .../TestSceneBeatmapAttributeText.cs | 98 +++++++++++++++++++ 1 file changed, 98 insertions(+) create mode 100644 osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapAttributeText.cs diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapAttributeText.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapAttributeText.cs new file mode 100644 index 0000000000..bf959d9862 --- /dev/null +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapAttributeText.cs @@ -0,0 +1,98 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Linq; +using NUnit.Framework; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Testing; +using osu.Game.Beatmaps; +using osu.Game.Models; +using osu.Game.Rulesets.Osu; +using osu.Game.Skinning.Components; +using osu.Game.Tests.Beatmaps; + +namespace osu.Game.Tests.Visual.UserInterface +{ + public partial class TestSceneBeatmapAttributeText : OsuTestScene + { + private readonly BeatmapAttributeText text; + + public TestSceneBeatmapAttributeText() + { + Child = text = new BeatmapAttributeText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre + }; + } + + [SetUp] + public void Setup() => Schedule(() => + { + Beatmap.Value = CreateWorkingBeatmap(new TestBeatmap(new OsuRuleset().RulesetInfo) + { + BeatmapInfo = + { + BPM = 100, + DifficultyName = "_Difficulty", + Status = BeatmapOnlineStatus.Loved, + Metadata = + { + Title = "_Title", + TitleUnicode = "_Title", + Artist = "_Artist", + ArtistUnicode = "_Artist", + Author = new RealmUser { Username = "_Creator" }, + Source = "_Source", + }, + Difficulty = + { + CircleSize = 1, + DrainRate = 2, + OverallDifficulty = 3, + ApproachRate = 4, + } + } + }); + }); + + [TestCase(BeatmapAttribute.CircleSize, "Circle Size: 1.00")] + [TestCase(BeatmapAttribute.HPDrain, "HP Drain: 2.00")] + [TestCase(BeatmapAttribute.Accuracy, "Accuracy: 3.00")] + [TestCase(BeatmapAttribute.ApproachRate, "Approach Rate: 4.00")] + [TestCase(BeatmapAttribute.Title, "Title: _Title")] + [TestCase(BeatmapAttribute.Artist, "Artist: _Artist")] + [TestCase(BeatmapAttribute.Creator, "Creator: _Creator")] + [TestCase(BeatmapAttribute.DifficultyName, "Difficulty: _Difficulty")] + [TestCase(BeatmapAttribute.Source, "Source: _Source")] + [TestCase(BeatmapAttribute.RankedStatus, "Beatmap Status: Loved")] + public void TestAttributeDisplay(BeatmapAttribute attribute, string expectedText) + { + AddStep($"set attribute: {attribute}", () => text.Attribute.Value = attribute); + AddAssert("check correct text", getText, () => Is.EqualTo(expectedText)); + } + + [Test] + public void TestChangeBeatmap() + { + AddStep("set title attribute", () => text.Attribute.Value = BeatmapAttribute.Title); + AddAssert("check initial title", getText, () => Is.EqualTo("Title: _Title")); + + AddStep("change to beatmap with another title", () => Beatmap.Value = CreateWorkingBeatmap(new TestBeatmap(new OsuRuleset().RulesetInfo) + { + BeatmapInfo = + { + Metadata = + { + Title = "Another" + } + } + })); + + AddAssert("check new title", getText, () => Is.EqualTo("Title: Another")); + } + + private string getText() => text.ChildrenOfType().Single().Text.ToString(); + } +} From 7416106321d6c8bf80bc971b088c1f6344d80ef5 Mon Sep 17 00:00:00 2001 From: jhk2601 Date: Thu, 17 Oct 2024 14:38:13 -0400 Subject: [PATCH 57/75] Fixes cursor rotating along with playfield when using Barrel Roll in standard --- osu.Game.Rulesets.Osu/Mods/OsuModBarrelRoll.cs | 12 ++++++++++++ osu.Game/Rulesets/Mods/ModBarrelRoll.cs | 2 +- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModBarrelRoll.cs b/osu.Game.Rulesets.Osu/Mods/OsuModBarrelRoll.cs index 2394cf92fc..8898faf7b8 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModBarrelRoll.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModBarrelRoll.cs @@ -2,10 +2,13 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Diagnostics; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects.Drawables; +using osu.Game.Rulesets.Osu.UI; +using osu.Game.Rulesets.UI; namespace osu.Game.Rulesets.Osu.Mods { @@ -25,5 +28,14 @@ namespace osu.Game.Rulesets.Osu.Mods } }; } + + public override void Update(Playfield playfield) + { + base.Update(playfield); + OsuPlayfield osuPlayfield = (OsuPlayfield)playfield; + Debug.Assert(osuPlayfield.Cursor != null); + + osuPlayfield.Cursor.ActiveCursor.Rotation = -CurrentRotation; + } } } diff --git a/osu.Game/Rulesets/Mods/ModBarrelRoll.cs b/osu.Game/Rulesets/Mods/ModBarrelRoll.cs index 4f90496308..67f9da37be 100644 --- a/osu.Game/Rulesets/Mods/ModBarrelRoll.cs +++ b/osu.Game/Rulesets/Mods/ModBarrelRoll.cs @@ -42,7 +42,7 @@ namespace osu.Game.Rulesets.Mods private PlayfieldAdjustmentContainer playfieldAdjustmentContainer = null!; - public void Update(Playfield playfield) + public virtual void Update(Playfield playfield) { playfieldAdjustmentContainer.Rotation = CurrentRotation = (Direction.Value == RotationDirection.Counterclockwise ? -1 : 1) * 360 * (float)(playfield.Time.Current / 60000 * SpinSpeed.Value); } From 7d0da79db78346424ca71d387cc9bad63d29c2f4 Mon Sep 17 00:00:00 2001 From: SupDos <6813986+SupDos@users.noreply.github.com> Date: Fri, 18 Oct 2024 01:47:54 +0200 Subject: [PATCH 58/75] Add Use relative size setting to ArgonSongProgress --- osu.Game/Screens/Play/HUD/ArgonSongProgress.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/osu.Game/Screens/Play/HUD/ArgonSongProgress.cs b/osu.Game/Screens/Play/HUD/ArgonSongProgress.cs index 92ac863e98..4f8ee8b374 100644 --- a/osu.Game/Screens/Play/HUD/ArgonSongProgress.cs +++ b/osu.Game/Screens/Play/HUD/ArgonSongProgress.cs @@ -30,6 +30,9 @@ namespace osu.Game.Screens.Play.HUD [SettingSource(typeof(SongProgressStrings), nameof(SongProgressStrings.ShowTime), nameof(SongProgressStrings.ShowTimeDescription))] public Bindable ShowTime { get; } = new BindableBool(true); + [SettingSource("Use relative size")] + public BindableBool UseRelativeSize { get; } = new BindableBool(true); + [SettingSource(typeof(SkinnableComponentStrings), nameof(SkinnableComponentStrings.Colour), nameof(SkinnableComponentStrings.ColourDescription))] public BindableColour4 AccentColour { get; } = new BindableColour4(Colour4.White); @@ -99,6 +102,11 @@ namespace osu.Game.Screens.Play.HUD ShowGraph.BindValueChanged(_ => updateGraphVisibility(), true); ShowTime.BindValueChanged(_ => info.FadeTo(ShowTime.Value ? 1 : 0, 200, Easing.In), true); AccentColour.BindValueChanged(_ => Colour = AccentColour.Value, true); + + // see comment in ArgonHealthDisplay.cs regarding RelativeSizeAxes + float previousWidth = Width; + UseRelativeSize.BindValueChanged(v => RelativeSizeAxes = v.NewValue ? Axes.X : Axes.None, true); + Width = previousWidth; } protected override void UpdateObjects(IEnumerable objects) From 8193038986d2dc205d83568ff2146220bc203dc5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 18 Oct 2024 13:30:22 +0900 Subject: [PATCH 59/75] Expose no-op constructors as `protected` --- osu.Game/Beatmaps/BeatmapInfo.cs | 2 +- osu.Game/Scoring/ScoreInfo.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapInfo.cs b/osu.Game/Beatmaps/BeatmapInfo.cs index 425fd98d27..f1463eb632 100644 --- a/osu.Game/Beatmaps/BeatmapInfo.cs +++ b/osu.Game/Beatmaps/BeatmapInfo.cs @@ -62,7 +62,7 @@ namespace osu.Game.Beatmaps } [UsedImplicitly] - private BeatmapInfo() + protected BeatmapInfo() { } diff --git a/osu.Game/Scoring/ScoreInfo.cs b/osu.Game/Scoring/ScoreInfo.cs index 92c18c9c1e..a3dabc7945 100644 --- a/osu.Game/Scoring/ScoreInfo.cs +++ b/osu.Game/Scoring/ScoreInfo.cs @@ -165,7 +165,7 @@ namespace osu.Game.Scoring } [UsedImplicitly] // Realm - private ScoreInfo() + protected ScoreInfo() { } From b455b9ad09a74284ab0d68b1e1088a8beb763ab6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 18 Oct 2024 08:39:23 +0200 Subject: [PATCH 60/75] Discard unused argument --- osu.Game/Skinning/Components/BeatmapAttributeText.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Skinning/Components/BeatmapAttributeText.cs b/osu.Game/Skinning/Components/BeatmapAttributeText.cs index 63ba6d1581..77e415b995 100644 --- a/osu.Game/Skinning/Components/BeatmapAttributeText.cs +++ b/osu.Game/Skinning/Components/BeatmapAttributeText.cs @@ -55,7 +55,7 @@ namespace osu.Game.Skinning.Components Attribute.BindValueChanged(_ => updateText()); Template.BindValueChanged(_ => updateText()); - beatmap.BindValueChanged(b => updateText()); + beatmap.BindValueChanged(_ => updateText()); updateText(); } From 8804769da1b44de957c09bdd911447a332969fe7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 18 Oct 2024 08:51:01 +0200 Subject: [PATCH 61/75] Use better exception messaging --- osu.Game/Skinning/Components/BeatmapAttributeText.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Skinning/Components/BeatmapAttributeText.cs b/osu.Game/Skinning/Components/BeatmapAttributeText.cs index 77e415b995..91ca7b8903 100644 --- a/osu.Game/Skinning/Components/BeatmapAttributeText.cs +++ b/osu.Game/Skinning/Components/BeatmapAttributeText.cs @@ -127,7 +127,7 @@ namespace osu.Game.Skinning.Components return BeatmapsetsStrings.ShowStatsBpm; default: - throw new ArgumentOutOfRangeException(); + throw new ArgumentOutOfRangeException(nameof(attribute), attribute, $@"Unrecognised {nameof(BeatmapAttribute)}"); } } @@ -175,7 +175,7 @@ namespace osu.Game.Skinning.Components return beatmap.Value.BeatmapInfo.StarRating.ToLocalisableString(@"F2"); default: - throw new ArgumentOutOfRangeException(); + throw new ArgumentOutOfRangeException(nameof(attribute), attribute, $@"Unrecognised {nameof(BeatmapAttribute)}"); } } From ca2bd640b4600944f28c550ab53b5ea087d824fe Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 18 Oct 2024 15:23:59 +0900 Subject: [PATCH 62/75] Update all dependencies (except realm, nunit, moq and deepclone) --- ...u.Game.Rulesets.EmptyFreeform.Tests.csproj | 2 +- .../osu.Game.Rulesets.Pippidon.Tests.csproj | 2 +- ....Game.Rulesets.EmptyScrolling.Tests.csproj | 2 +- .../osu.Game.Rulesets.Pippidon.Tests.csproj | 2 +- .../osu.Game.Benchmarks.csproj | 2 +- .../osu.Game.Rulesets.Catch.Tests.csproj | 2 +- .../osu.Game.Rulesets.Mania.Tests.csproj | 2 +- .../osu.Game.Rulesets.Osu.Tests.csproj | 2 +- .../osu.Game.Rulesets.Taiko.Tests.csproj | 2 +- osu.Game.Tests/osu.Game.Tests.csproj | 2 +- .../osu.Game.Tournament.Tests.csproj | 2 +- osu.Game/osu.Game.csproj | 22 +++++++++---------- 12 files changed, 22 insertions(+), 22 deletions(-) diff --git a/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/osu.Game.Rulesets.EmptyFreeform.Tests.csproj b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/osu.Game.Rulesets.EmptyFreeform.Tests.csproj index 7d43eb2b05..f77cda1533 100644 --- a/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/osu.Game.Rulesets.EmptyFreeform.Tests.csproj +++ b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/osu.Game.Rulesets.EmptyFreeform.Tests.csproj @@ -9,7 +9,7 @@ false - + diff --git a/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj index 7dc8a1336b..47cabaddb1 100644 --- a/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj +++ b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj @@ -9,7 +9,7 @@ false - + diff --git a/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/osu.Game.Rulesets.EmptyScrolling.Tests.csproj b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/osu.Game.Rulesets.EmptyScrolling.Tests.csproj index 9c4c8217f0..a7d62291d0 100644 --- a/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/osu.Game.Rulesets.EmptyScrolling.Tests.csproj +++ b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/osu.Game.Rulesets.EmptyScrolling.Tests.csproj @@ -9,7 +9,7 @@ false - + diff --git a/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj index 7dc8a1336b..47cabaddb1 100644 --- a/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj +++ b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj @@ -9,7 +9,7 @@ false - + diff --git a/osu.Game.Benchmarks/osu.Game.Benchmarks.csproj b/osu.Game.Benchmarks/osu.Game.Benchmarks.csproj index af84ee47f1..9764c71493 100644 --- a/osu.Game.Benchmarks/osu.Game.Benchmarks.csproj +++ b/osu.Game.Benchmarks/osu.Game.Benchmarks.csproj @@ -7,7 +7,7 @@ - + diff --git a/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj b/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj index 619081c754..b434d6aaf9 100644 --- a/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj +++ b/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj @@ -1,7 +1,7 @@  - + diff --git a/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj b/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj index eee06acdb8..e7abd47881 100644 --- a/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj +++ b/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj @@ -1,7 +1,7 @@  - + diff --git a/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj b/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj index ea54c8d313..5ea231e606 100644 --- a/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj +++ b/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj @@ -1,7 +1,7 @@  - + diff --git a/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj b/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj index a2420fc679..2170009ae8 100644 --- a/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj +++ b/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj @@ -1,7 +1,7 @@  - + diff --git a/osu.Game.Tests/osu.Game.Tests.csproj b/osu.Game.Tests/osu.Game.Tests.csproj index c0bbdfb4ed..28a1d4d021 100644 --- a/osu.Game.Tests/osu.Game.Tests.csproj +++ b/osu.Game.Tests/osu.Game.Tests.csproj @@ -1,8 +1,8 @@  + - diff --git a/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj b/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj index 8f1d7114b1..04683cd83b 100644 --- a/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj +++ b/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj @@ -4,7 +4,7 @@ osu.Game.Tournament.Tests.TournamentTestRunner - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index f982b11ad5..10c72ee2f1 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -18,16 +18,16 @@ - + - + - - - - - - + + + + + + @@ -37,11 +37,11 @@ - + - + - + From 7ca5f91c15ea6845ca658697261ea95227359dd4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 18 Oct 2024 15:26:17 +0900 Subject: [PATCH 63/75] Update signalr exceptions in line with deprecated `ctor` --- osu.Game/Online/Multiplayer/InvalidPasswordException.cs | 9 --------- .../Online/Multiplayer/InvalidStateChangeException.cs | 6 ------ osu.Game/Online/Multiplayer/InvalidStateException.cs | 6 ------ osu.Game/Online/Multiplayer/NotHostException.cs | 6 ------ osu.Game/Online/Multiplayer/NotJoinedRoomException.cs | 6 ------ osu.Game/Online/Multiplayer/UserBlockedException.cs | 6 ------ osu.Game/Online/Multiplayer/UserBlocksPMsException.cs | 6 ------ 7 files changed, 45 deletions(-) diff --git a/osu.Game/Online/Multiplayer/InvalidPasswordException.cs b/osu.Game/Online/Multiplayer/InvalidPasswordException.cs index d3da8f491b..b76a1cc05d 100644 --- a/osu.Game/Online/Multiplayer/InvalidPasswordException.cs +++ b/osu.Game/Online/Multiplayer/InvalidPasswordException.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Runtime.Serialization; using Microsoft.AspNetCore.SignalR; namespace osu.Game.Online.Multiplayer @@ -10,13 +9,5 @@ namespace osu.Game.Online.Multiplayer [Serializable] public class InvalidPasswordException : HubException { - public InvalidPasswordException() - { - } - - protected InvalidPasswordException(SerializationInfo info, StreamingContext context) - : base(info, context) - { - } } } diff --git a/osu.Game/Online/Multiplayer/InvalidStateChangeException.cs b/osu.Game/Online/Multiplayer/InvalidStateChangeException.cs index 4c793dba68..2bae31196a 100644 --- a/osu.Game/Online/Multiplayer/InvalidStateChangeException.cs +++ b/osu.Game/Online/Multiplayer/InvalidStateChangeException.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Runtime.Serialization; using Microsoft.AspNetCore.SignalR; namespace osu.Game.Online.Multiplayer @@ -14,10 +13,5 @@ namespace osu.Game.Online.Multiplayer : base($"Cannot change from {oldState} to {newState}") { } - - protected InvalidStateChangeException(SerializationInfo info, StreamingContext context) - : base(info, context) - { - } } } diff --git a/osu.Game/Online/Multiplayer/InvalidStateException.cs b/osu.Game/Online/Multiplayer/InvalidStateException.cs index 27b111a781..c9705e9e53 100644 --- a/osu.Game/Online/Multiplayer/InvalidStateException.cs +++ b/osu.Game/Online/Multiplayer/InvalidStateException.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Runtime.Serialization; using Microsoft.AspNetCore.SignalR; namespace osu.Game.Online.Multiplayer @@ -14,10 +13,5 @@ namespace osu.Game.Online.Multiplayer : base(message) { } - - protected InvalidStateException(SerializationInfo info, StreamingContext context) - : base(info, context) - { - } } } diff --git a/osu.Game/Online/Multiplayer/NotHostException.cs b/osu.Game/Online/Multiplayer/NotHostException.cs index cd43b13e52..f4fd217c87 100644 --- a/osu.Game/Online/Multiplayer/NotHostException.cs +++ b/osu.Game/Online/Multiplayer/NotHostException.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Runtime.Serialization; using Microsoft.AspNetCore.SignalR; namespace osu.Game.Online.Multiplayer @@ -14,10 +13,5 @@ namespace osu.Game.Online.Multiplayer : base("User is attempting to perform a host level operation while not the host") { } - - protected NotHostException(SerializationInfo info, StreamingContext context) - : base(info, context) - { - } } } diff --git a/osu.Game/Online/Multiplayer/NotJoinedRoomException.cs b/osu.Game/Online/Multiplayer/NotJoinedRoomException.cs index 0a96406c16..72773e28db 100644 --- a/osu.Game/Online/Multiplayer/NotJoinedRoomException.cs +++ b/osu.Game/Online/Multiplayer/NotJoinedRoomException.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Runtime.Serialization; using Microsoft.AspNetCore.SignalR; namespace osu.Game.Online.Multiplayer @@ -14,10 +13,5 @@ namespace osu.Game.Online.Multiplayer : base("This user has not yet joined a multiplayer room.") { } - - protected NotJoinedRoomException(SerializationInfo info, StreamingContext context) - : base(info, context) - { - } } } diff --git a/osu.Game/Online/Multiplayer/UserBlockedException.cs b/osu.Game/Online/Multiplayer/UserBlockedException.cs index e964b13c75..58e86d9f32 100644 --- a/osu.Game/Online/Multiplayer/UserBlockedException.cs +++ b/osu.Game/Online/Multiplayer/UserBlockedException.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Runtime.Serialization; using Microsoft.AspNetCore.SignalR; namespace osu.Game.Online.Multiplayer @@ -16,10 +15,5 @@ namespace osu.Game.Online.Multiplayer : base(MESSAGE) { } - - protected UserBlockedException(SerializationInfo info, StreamingContext context) - : base(info, context) - { - } } } diff --git a/osu.Game/Online/Multiplayer/UserBlocksPMsException.cs b/osu.Game/Online/Multiplayer/UserBlocksPMsException.cs index 14ed6fc212..0ea583ae2c 100644 --- a/osu.Game/Online/Multiplayer/UserBlocksPMsException.cs +++ b/osu.Game/Online/Multiplayer/UserBlocksPMsException.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Runtime.Serialization; using Microsoft.AspNetCore.SignalR; namespace osu.Game.Online.Multiplayer @@ -16,10 +15,5 @@ namespace osu.Game.Online.Multiplayer : base(MESSAGE) { } - - protected UserBlocksPMsException(SerializationInfo info, StreamingContext context) - : base(info, context) - { - } } } From 8b4565b3d904389f02d86b328e7160af59b41248 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 18 Oct 2024 09:42:08 +0200 Subject: [PATCH 64/75] Silence nullref inspection in test --- osu.Game.Tests/Database/BeatmapImporterTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Database/BeatmapImporterTests.cs b/osu.Game.Tests/Database/BeatmapImporterTests.cs index 0eac70f9c8..38746f2567 100644 --- a/osu.Game.Tests/Database/BeatmapImporterTests.cs +++ b/osu.Game.Tests/Database/BeatmapImporterTests.cs @@ -716,7 +716,7 @@ namespace osu.Game.Tests.Database { foreach (var entry in zip.Entries.ToArray()) { - if (entry.Key.EndsWith(".osu", StringComparison.InvariantCulture)) + if (entry.Key!.EndsWith(".osu", StringComparison.InvariantCulture)) zip.RemoveEntry(entry); } From 2de5e3392ef8928ca57d4416069a453ade1673ae Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 18 Oct 2024 17:16:42 +0900 Subject: [PATCH 65/75] Only add as many values as are replaced --- osu.Game/Skinning/Components/BeatmapAttributeText.cs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/osu.Game/Skinning/Components/BeatmapAttributeText.cs b/osu.Game/Skinning/Components/BeatmapAttributeText.cs index 91ca7b8903..d8c864c8d8 100644 --- a/osu.Game/Skinning/Components/BeatmapAttributeText.cs +++ b/osu.Game/Skinning/Components/BeatmapAttributeText.cs @@ -76,8 +76,13 @@ namespace osu.Game.Skinning.Components foreach (var type in Enum.GetValues()) { - numberedTemplate = numberedTemplate.Replace($"{{{{{type}}}}}", $"{{{values.Count}}}"); - values.Add(getValueString(type)); + string replaced = numberedTemplate.Replace($@"{{{{{type}}}}}", $@"{{{values.Count}}}"); + + if (numberedTemplate != replaced) + { + numberedTemplate = replaced; + values.Add(getValueString(type)); + } } text.Text = LocalisableString.Format(numberedTemplate, values.ToArray()); From e27aa0c761408cc16f2a0fe1c70e74bfecd85a89 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 18 Oct 2024 17:17:57 +0900 Subject: [PATCH 66/75] Return empty strings rather than erroring Preventing malicious actors from permanently destroying games via skins. --- osu.Game/Skinning/Components/BeatmapAttributeText.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Skinning/Components/BeatmapAttributeText.cs b/osu.Game/Skinning/Components/BeatmapAttributeText.cs index d8c864c8d8..4ecf5acea7 100644 --- a/osu.Game/Skinning/Components/BeatmapAttributeText.cs +++ b/osu.Game/Skinning/Components/BeatmapAttributeText.cs @@ -132,7 +132,7 @@ namespace osu.Game.Skinning.Components return BeatmapsetsStrings.ShowStatsBpm; default: - throw new ArgumentOutOfRangeException(nameof(attribute), attribute, $@"Unrecognised {nameof(BeatmapAttribute)}"); + return string.Empty; } } @@ -180,7 +180,7 @@ namespace osu.Game.Skinning.Components return beatmap.Value.BeatmapInfo.StarRating.ToLocalisableString(@"F2"); default: - throw new ArgumentOutOfRangeException(nameof(attribute), attribute, $@"Unrecognised {nameof(BeatmapAttribute)}"); + return string.Empty; } } From bb4f3c71e033fc71d9e769af7eaba6015c4e25df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 18 Oct 2024 11:39:52 +0200 Subject: [PATCH 67/75] Add localisation support for "use relative size" setting label --- .../Localisation/SkinComponents/SkinnableComponentStrings.cs | 5 +++++ osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs | 3 ++- osu.Game/Screens/Play/HUD/ArgonSongProgress.cs | 2 +- 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/osu.Game/Localisation/SkinComponents/SkinnableComponentStrings.cs b/osu.Game/Localisation/SkinComponents/SkinnableComponentStrings.cs index 33fda23cb0..b21446e18a 100644 --- a/osu.Game/Localisation/SkinComponents/SkinnableComponentStrings.cs +++ b/osu.Game/Localisation/SkinComponents/SkinnableComponentStrings.cs @@ -79,6 +79,11 @@ namespace osu.Game.Localisation.SkinComponents /// public static LocalisableString TextColourDescription => new TranslatableString(getKey(@"text_colour_description"), @"The colour of the text."); + /// + /// "Use relative size" + /// + public static LocalisableString UseRelativeSize => new TranslatableString(getKey(@"use_relative_size"), @"Use relative size"); + private static string getKey(string key) => $@"{prefix}:{key}"; } } diff --git a/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs b/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs index 71996718d9..44f021f93e 100644 --- a/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs +++ b/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs @@ -13,6 +13,7 @@ using osu.Framework.Layout; using osu.Framework.Threading; using osu.Framework.Utils; using osu.Game.Configuration; +using osu.Game.Localisation.SkinComponents; using osu.Game.Rulesets.Judgements; using osu.Game.Screens.Play.HUD.ArgonHealthDisplayParts; using osu.Game.Skinning; @@ -33,7 +34,7 @@ namespace osu.Game.Screens.Play.HUD Precision = 1 }; - [SettingSource("Use relative size")] + [SettingSource(typeof(SkinnableComponentStrings), nameof(SkinnableComponentStrings.UseRelativeSize))] public BindableBool UseRelativeSize { get; } = new BindableBool(true); private ArgonHealthDisplayBar mainBar = null!; diff --git a/osu.Game/Screens/Play/HUD/ArgonSongProgress.cs b/osu.Game/Screens/Play/HUD/ArgonSongProgress.cs index 4f8ee8b374..8dc5d60352 100644 --- a/osu.Game/Screens/Play/HUD/ArgonSongProgress.cs +++ b/osu.Game/Screens/Play/HUD/ArgonSongProgress.cs @@ -30,7 +30,7 @@ namespace osu.Game.Screens.Play.HUD [SettingSource(typeof(SongProgressStrings), nameof(SongProgressStrings.ShowTime), nameof(SongProgressStrings.ShowTimeDescription))] public Bindable ShowTime { get; } = new BindableBool(true); - [SettingSource("Use relative size")] + [SettingSource(typeof(SkinnableComponentStrings), nameof(SkinnableComponentStrings.UseRelativeSize))] public BindableBool UseRelativeSize { get; } = new BindableBool(true); [SettingSource(typeof(SkinnableComponentStrings), nameof(SkinnableComponentStrings.Colour), nameof(SkinnableComponentStrings.ColourDescription))] From 47f10693c49ed47c21912ff6172ce6d092052b2d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 18 Oct 2024 11:43:03 +0200 Subject: [PATCH 68/75] Add relative size toggle to `DefaultSongProgress` too --- osu.Game/Screens/Play/HUD/DefaultSongProgress.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/osu.Game/Screens/Play/HUD/DefaultSongProgress.cs b/osu.Game/Screens/Play/HUD/DefaultSongProgress.cs index 4e41901ee3..672017750d 100644 --- a/osu.Game/Screens/Play/HUD/DefaultSongProgress.cs +++ b/osu.Game/Screens/Play/HUD/DefaultSongProgress.cs @@ -37,6 +37,9 @@ namespace osu.Game.Screens.Play.HUD [SettingSource(typeof(SongProgressStrings), nameof(SongProgressStrings.ShowTime), nameof(SongProgressStrings.ShowTimeDescription))] public Bindable ShowTime { get; } = new BindableBool(true); + [SettingSource(typeof(SkinnableComponentStrings), nameof(SkinnableComponentStrings.UseRelativeSize))] + public BindableBool UseRelativeSize { get; } = new BindableBool(true); + [SettingSource(typeof(SkinnableComponentStrings), nameof(SkinnableComponentStrings.Colour), nameof(SkinnableComponentStrings.ColourDescription))] public BindableColour4 AccentColour { get; } = new BindableColour4(Colour4.White); @@ -83,6 +86,11 @@ namespace osu.Game.Screens.Play.HUD private void load(OsuColour colours) { graph.FillColour = bar.FillColour = colours.BlueLighter; + + // see comment in ArgonHealthDisplay.cs regarding RelativeSizeAxes + float previousWidth = Width; + UseRelativeSize.BindValueChanged(v => RelativeSizeAxes = v.NewValue ? Axes.X : Axes.None, true); + Width = previousWidth; } protected override void LoadComplete() From df90b726b9005f7fb645c93f310f47d9417a2601 Mon Sep 17 00:00:00 2001 From: Wezwery Date: Sat, 19 Oct 2024 03:08:08 +0300 Subject: [PATCH 69/75] Add highlight to combo and accuracy when max. --- .../BeatmapSet/Scores/TopScoreStatisticsSection.cs | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs b/osu.Game/Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs index 17704f63ee..1ecc3d53fa 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs @@ -9,6 +9,7 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions.LocalisationExtensions; using osu.Framework.Graphics; +using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; @@ -96,10 +97,17 @@ namespace osu.Game.Overlays.BeatmapSet.Scores } [BackgroundDependencyLoader] - private void load() + private void load(OsuColour colours) { if (score != null) + { totalScoreColumn.Current = scoreManager.GetBindableTotalScoreString(score); + + if (score.Accuracy == 1.0) accuracyColumn.TextColour = colours.GreenLight; +#pragma warning disable CS0618 + if (score.MaxCombo == score.BeatmapInfo!.MaxCombo) maxComboColumn.TextColour = colours.GreenLight; +#pragma warning restore CS0618 + } } private ScoreInfo score; @@ -227,6 +235,10 @@ namespace osu.Game.Overlays.BeatmapSet.Scores { set => text.Text = value; } + public ColourInfo TextColour + { + set => text.Colour = value; + } public Drawable Drawable { From 47936c7b02e784d97951418a585b9a4dade751a5 Mon Sep 17 00:00:00 2001 From: Wezwery Date: Sat, 19 Oct 2024 13:41:36 +0300 Subject: [PATCH 70/75] Add blank line to TopScoreStatisticsSection.cs:238 --- osu.Game/Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs b/osu.Game/Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs index 1ecc3d53fa..f1eed81e56 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs @@ -235,6 +235,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores { set => text.Text = value; } + public ColourInfo TextColour { set => text.Colour = value; From fc0ec20db98fdf54683cc3b3f25d179662a530e7 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Mon, 21 Oct 2024 18:26:38 +0900 Subject: [PATCH 71/75] Change convert-to-ternary warning to hint --- osu.sln.DotSettings | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.sln.DotSettings b/osu.sln.DotSettings index 4a2ef97520..ccd6db354b 100644 --- a/osu.sln.DotSettings +++ b/osu.sln.DotSettings @@ -69,7 +69,7 @@ DO_NOT_SHOW HINT WARNING - WARNING + HINT WARNING WARNING DO_NOT_SHOW From 59e9ed7bac4ae111e71f2fff4c601ffaefe9a7cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 21 Oct 2024 12:44:01 +0200 Subject: [PATCH 72/75] Add test coverage --- .../Visual/Online/TestSceneScoresContainer.cs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/osu.Game.Tests/Visual/Online/TestSceneScoresContainer.cs b/osu.Game.Tests/Visual/Online/TestSceneScoresContainer.cs index c17d746232..ad0c5f9247 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneScoresContainer.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneScoresContainer.cs @@ -147,6 +147,18 @@ namespace osu.Game.Tests.Visual.Online AddUntilStep("wait for scores displayed", () => scoresContainer.ChildrenOfType().Any()); AddAssert("best score displayed", () => scoresContainer.ChildrenOfType().Count() == 2); + AddStep("Load scores with personal best FC", () => + { + var allScores = createScores(); + allScores.UserScore = createUserBest(); + allScores.UserScore.Score.Accuracy = 1; + scoresContainer.Beatmap.Value.MaxCombo = allScores.UserScore.Score.MaxCombo = 1337; + scoresContainer.Scores = allScores; + }); + + AddUntilStep("wait for scores displayed", () => scoresContainer.ChildrenOfType().Any()); + AddAssert("best score displayed", () => scoresContainer.ChildrenOfType().Count() == 2); + AddStep("Load scores with personal best (null position)", () => { var allScores = createScores(); From a90ad6349358a4eecabaf42721f6340f60b6dd81 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 21 Oct 2024 12:47:41 +0200 Subject: [PATCH 73/75] Change property type Nobody is supposed to be using `ColourInfo` directly, really. --- .../Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs b/osu.Game/Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs index f1eed81e56..9250a82902 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs @@ -236,7 +236,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores set => text.Text = value; } - public ColourInfo TextColour + public Colour4 TextColour { set => text.Colour = value; } From 0a15eec7de9a9d435f200453af736859f0cda658 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 21 Oct 2024 13:52:17 +0200 Subject: [PATCH 74/75] Remove unused using directive --- osu.Game/Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs b/osu.Game/Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs index 9250a82902..e8833fa0a3 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs @@ -9,7 +9,6 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions.LocalisationExtensions; using osu.Framework.Graphics; -using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; From cbaee986742fd5dfa011df2b33e2793d3e42bc94 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 22 Oct 2024 01:39:05 +0900 Subject: [PATCH 75/75] Don't delete scores when deleting beatmaps The score model's spec allows for null `BeatmapInfo` so the reasoning of the inline comment is no longer valid. We match based on hash these days. --- osu.Game/Database/RealmAccess.cs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/osu.Game/Database/RealmAccess.cs b/osu.Game/Database/RealmAccess.cs index a437b3313e..eb7182820b 100644 --- a/osu.Game/Database/RealmAccess.cs +++ b/osu.Game/Database/RealmAccess.cs @@ -376,10 +376,6 @@ namespace osu.Game.Database { foreach (var beatmap in beatmapSet.Beatmaps) { - // Cascade delete related scores, else they will have a null beatmap against the model's spec. - foreach (var score in beatmap.Scores) - realm.Remove(score); - realm.Remove(beatmap.Metadata); realm.Remove(beatmap); }