From 96e09605d8fabe836396d2cb92d0deabaacb93a2 Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Tue, 29 Jun 2021 12:33:40 +0800 Subject: [PATCH 01/43] Osu random mod improvements - Reduce "jump streams" by increasing maximum jump angle and variance in jump angle - Reduce weird jumps to sliders by shifting hit circles in front of sliders --- osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs | 66 ++++++++++++++++++++-- 1 file changed, 61 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs index d1212096bf..d3a7f4fc74 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs @@ -26,6 +26,11 @@ namespace osu.Game.Rulesets.Osu.Mods private static readonly float playfield_diagonal = OsuPlayfield.BASE_SIZE.LengthFast; + /// + /// Number of previous hit circles to be shifted together when a slider needs to be moved. + /// + private const int shift_object_count = 10; + private Random rng; public void ApplyToBeatmap(IBeatmap beatmap) @@ -43,14 +48,22 @@ namespace osu.Game.Rulesets.Osu.Mods float rateOfChangeMultiplier = 0; + int cntSinceNewCombo = 0; + for (int i = 0; i < hitObjects.Count; i++) { var hitObject = hitObjects[i]; var current = new RandomObjectInfo(hitObject); - // rateOfChangeMultiplier only changes every i iterations to prevent shaky-line-shaped streams - if (i % 3 == 0) + // rateOfChangeMultiplier only changes every 5 iterations in a combo + // to prevent shaky-line-shaped streams + if (hitObject.NewCombo) + cntSinceNewCombo = 0; + else + cntSinceNewCombo++; + + if (cntSinceNewCombo % 5 == 0) rateOfChangeMultiplier = (float)rng.NextDouble() * 2 - 1; if (hitObject is Spinner) @@ -67,7 +80,24 @@ namespace osu.Game.Rulesets.Osu.Mods current.EndPositionRandomised = current.PositionRandomised; if (hitObject is Slider slider) - moveSliderIntoPlayfield(slider, current); + { + Vector2 shift = moveSliderIntoPlayfield(slider, current); + + if (shift != Vector2.Zero) + { + var toBeShifted = new List(); + + for (int j = i - 1; j >= i - shift_object_count && j >= 0; j--) + { + if (!(hitObjects[j] is HitCircle)) break; + + toBeShifted.Add(hitObjects[j]); + } + + if (toBeShifted.Count > 0) + applyDecreasingShift(toBeShifted, shift); + } + } previous = current; } @@ -94,7 +124,9 @@ namespace osu.Game.Rulesets.Osu.Mods // The max. angle (relative to the angle of the vector pointing from the 2nd last to the last hit object) // is proportional to the distance between the last and the current hit object // to allow jumps and prevent too sharp turns during streams. - var randomAngleRad = rateOfChangeMultiplier * 2 * Math.PI * distanceToPrev / playfield_diagonal; + + // Allow maximum jump angle when jump distance is more than half of playfield diagonal length + var randomAngleRad = rateOfChangeMultiplier * 2 * Math.PI * Math.Min(1f, distanceToPrev / (playfield_diagonal * 0.5f)); current.AngleRad = (float)randomAngleRad + previous.AngleRad; if (current.AngleRad < 0) @@ -122,10 +154,13 @@ namespace osu.Game.Rulesets.Osu.Mods /// /// Moves the and all necessary nested s into the if they aren't already. /// - private void moveSliderIntoPlayfield(Slider slider, RandomObjectInfo currentObjectInfo) + /// The that this slider has been shifted by. + private Vector2 moveSliderIntoPlayfield(Slider slider, RandomObjectInfo currentObjectInfo) { var minMargin = getMinSliderMargin(slider); + var prevPosition = slider.Position; + slider.Position = new Vector2( Math.Clamp(slider.Position.X, minMargin.Left, OsuPlayfield.BASE_SIZE.X - minMargin.Right), Math.Clamp(slider.Position.Y, minMargin.Top, OsuPlayfield.BASE_SIZE.Y - minMargin.Bottom) @@ -135,6 +170,27 @@ namespace osu.Game.Rulesets.Osu.Mods currentObjectInfo.EndPositionRandomised = slider.EndPosition; shiftNestedObjects(slider, currentObjectInfo.PositionRandomised - currentObjectInfo.PositionOriginal); + + return slider.Position - prevPosition; + } + + /// + /// Decreasingly shift a list of s by a specified amount. + /// The first item in the list is shifted by the largest amount, while the last item is shifted by the smallest amount. + /// + /// The list of hit objects to be shifted. + /// The amount to be shifted. + private void applyDecreasingShift(IList hitObjects, Vector2 shift) + { + for (int i = 0; i < hitObjects.Count; i++) + { + Vector2 position = hitObjects[i].Position + shift * ((hitObjects.Count - i) / (float)(hitObjects.Count + 1)); + + position.X = MathHelper.Clamp(position.X, 0, OsuPlayfield.BASE_SIZE.X); + position.Y = MathHelper.Clamp(position.Y, 0, OsuPlayfield.BASE_SIZE.Y); + + hitObjects[i].Position = position; + } } /// From 0c5777c2c82b4cd0cc1e7bbed59833c3e0d81a63 Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Tue, 29 Jun 2021 12:56:05 +0800 Subject: [PATCH 02/43] Added comments --- osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs index d3a7f4fc74..25bdac7be3 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs @@ -89,6 +89,7 @@ namespace osu.Game.Rulesets.Osu.Mods for (int j = i - 1; j >= i - shift_object_count && j >= 0; j--) { + // only shift hit circles if (!(hitObjects[j] is HitCircle)) break; toBeShifted.Add(hitObjects[j]); @@ -184,6 +185,8 @@ namespace osu.Game.Rulesets.Osu.Mods { for (int i = 0; i < hitObjects.Count; i++) { + // The first object is shifted by a vector slightly smaller than shift + // The last object is shifted by a vector slightly larger than zero Vector2 position = hitObjects[i].Position + shift * ((hitObjects.Count - i) / (float)(hitObjects.Count + 1)); position.X = MathHelper.Clamp(position.X, 0, OsuPlayfield.BASE_SIZE.X); From 2722565204b59c3b99be66ea801127863e28b3e8 Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Tue, 29 Jun 2021 13:36:30 +0800 Subject: [PATCH 03/43] Take circle radius into account when clamping to playfield --- osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs | 36 ++++++++++++++-------- 1 file changed, 23 insertions(+), 13 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs index 25bdac7be3..62ca5e5fb4 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs @@ -74,6 +74,10 @@ namespace osu.Game.Rulesets.Osu.Mods applyRandomisation(rateOfChangeMultiplier, previous, current); + // Move hit objects back into the playfield if they are outside of it, + // which would sometimes happen during big jumps otherwise. + current.PositionRandomised = clampToPlayfield(current.PositionRandomised, (float)hitObject.Radius); + hitObject.Position = current.PositionRandomised; // update end position as it may have changed as a result of the position update. @@ -142,14 +146,7 @@ namespace osu.Game.Rulesets.Osu.Mods current.AngleRad = (float)Math.Atan2(posRelativeToPrev.Y, posRelativeToPrev.X); - var position = previous.EndPositionRandomised + posRelativeToPrev; - - // Move hit objects back into the playfield if they are outside of it, - // which would sometimes happen during big jumps otherwise. - position.X = MathHelper.Clamp(position.X, 0, OsuPlayfield.BASE_SIZE.X); - position.Y = MathHelper.Clamp(position.Y, 0, OsuPlayfield.BASE_SIZE.Y); - - current.PositionRandomised = position; + current.PositionRandomised = previous.EndPositionRandomised + posRelativeToPrev; } /// @@ -185,14 +182,12 @@ namespace osu.Game.Rulesets.Osu.Mods { for (int i = 0; i < hitObjects.Count; i++) { + var hitObject = hitObjects[i]; // The first object is shifted by a vector slightly smaller than shift // The last object is shifted by a vector slightly larger than zero - Vector2 position = hitObjects[i].Position + shift * ((hitObjects.Count - i) / (float)(hitObjects.Count + 1)); + Vector2 position = hitObject.Position + shift * ((hitObjects.Count - i) / (float)(hitObjects.Count + 1)); - position.X = MathHelper.Clamp(position.X, 0, OsuPlayfield.BASE_SIZE.X); - position.Y = MathHelper.Clamp(position.Y, 0, OsuPlayfield.BASE_SIZE.Y); - - hitObjects[i].Position = position; + hitObject.Position = clampToPlayfield(position, (float)hitObject.Radius); } } @@ -217,6 +212,13 @@ namespace osu.Game.Rulesets.Osu.Mods minMargin.Left = Math.Min(minMargin.Left, OsuPlayfield.BASE_SIZE.X - minMargin.Right); minMargin.Top = Math.Min(minMargin.Top, OsuPlayfield.BASE_SIZE.Y - minMargin.Bottom); + var radius = (float)slider.Radius; + + minMargin.Left += radius; + minMargin.Right += radius; + minMargin.Top += radius; + minMargin.Bottom += radius; + return minMargin; } @@ -236,6 +238,14 @@ namespace osu.Game.Rulesets.Osu.Mods } } + private Vector2 clampToPlayfield(Vector2 position, float radius) + { + position.X = MathHelper.Clamp(position.X, radius, OsuPlayfield.BASE_SIZE.X - radius); + position.Y = MathHelper.Clamp(position.Y, radius, OsuPlayfield.BASE_SIZE.Y - radius); + + return position; + } + private class RandomObjectInfo { public float AngleRad { get; set; } From 8d1eae7c705c95f159304b262f658348ce2c84c8 Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Tue, 29 Jun 2021 14:25:45 +0800 Subject: [PATCH 04/43] Use `IndexInCurrentCombo` --- osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs index 62ca5e5fb4..78a49f8e91 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs @@ -48,8 +48,6 @@ namespace osu.Game.Rulesets.Osu.Mods float rateOfChangeMultiplier = 0; - int cntSinceNewCombo = 0; - for (int i = 0; i < hitObjects.Count; i++) { var hitObject = hitObjects[i]; @@ -58,12 +56,7 @@ namespace osu.Game.Rulesets.Osu.Mods // rateOfChangeMultiplier only changes every 5 iterations in a combo // to prevent shaky-line-shaped streams - if (hitObject.NewCombo) - cntSinceNewCombo = 0; - else - cntSinceNewCombo++; - - if (cntSinceNewCombo % 5 == 0) + if (hitObject.IndexInCurrentCombo % 5 == 0) rateOfChangeMultiplier = (float)rng.NextDouble() * 2 - 1; if (hitObject is Spinner) From 3f185a062234cf8f9e86511a365ea496c93d7506 Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Wed, 30 Jun 2021 10:35:06 +0800 Subject: [PATCH 05/43] Fixed an exception when clamping large sliders --- osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs index 78a49f8e91..e0a3e83241 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs @@ -152,10 +152,15 @@ namespace osu.Game.Rulesets.Osu.Mods var prevPosition = slider.Position; - slider.Position = new Vector2( - Math.Clamp(slider.Position.X, minMargin.Left, OsuPlayfield.BASE_SIZE.X - minMargin.Right), - Math.Clamp(slider.Position.Y, minMargin.Top, OsuPlayfield.BASE_SIZE.Y - minMargin.Bottom) - ); + var newX = minMargin.Left + minMargin.Right > OsuPlayfield.BASE_SIZE.X + ? currentObjectInfo.PositionOriginal.X + : Math.Clamp(slider.Position.X, minMargin.Left, OsuPlayfield.BASE_SIZE.X - minMargin.Right); + + var newY = minMargin.Top + minMargin.Bottom > OsuPlayfield.BASE_SIZE.Y + ? currentObjectInfo.PositionOriginal.Y + : Math.Clamp(slider.Position.Y, minMargin.Top, OsuPlayfield.BASE_SIZE.Y - minMargin.Bottom); + + slider.Position = new Vector2(newX, newY); currentObjectInfo.PositionRandomised = slider.Position; currentObjectInfo.EndPositionRandomised = slider.EndPosition; From 3c1f0452a2263faa5b6e017fbf02aff40c4b4c3d Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Thu, 1 Jul 2021 10:06:14 +0800 Subject: [PATCH 06/43] Refactor and rename `getMinSliderMargin` to `getSliderBoundingBox`. --- osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs | 57 ++++++++++++---------- 1 file changed, 31 insertions(+), 26 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs index e0a3e83241..71c070d91b 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs @@ -4,7 +4,7 @@ using System; using System.Collections.Generic; using System.Linq; -using osu.Framework.Graphics; +using osu.Framework.Graphics.Primitives; using osu.Framework.Utils; using osu.Game.Beatmaps; using osu.Game.Rulesets.Mods; @@ -148,19 +148,14 @@ namespace osu.Game.Rulesets.Osu.Mods /// The that this slider has been shifted by. private Vector2 moveSliderIntoPlayfield(Slider slider, RandomObjectInfo currentObjectInfo) { - var minMargin = getMinSliderMargin(slider); + var minMargin = getSliderBoundingBox(slider); var prevPosition = slider.Position; - var newX = minMargin.Left + minMargin.Right > OsuPlayfield.BASE_SIZE.X - ? currentObjectInfo.PositionOriginal.X - : Math.Clamp(slider.Position.X, minMargin.Left, OsuPlayfield.BASE_SIZE.X - minMargin.Right); - - var newY = minMargin.Top + minMargin.Bottom > OsuPlayfield.BASE_SIZE.Y - ? currentObjectInfo.PositionOriginal.Y - : Math.Clamp(slider.Position.Y, minMargin.Top, OsuPlayfield.BASE_SIZE.Y - minMargin.Bottom); - - slider.Position = new Vector2(newX, newY); + slider.Position = new Vector2( + Math.Clamp(slider.Position.X, minMargin.Left, minMargin.Right), + Math.Clamp(slider.Position.Y, minMargin.Top, minMargin.Bottom) + ); currentObjectInfo.PositionRandomised = slider.Position; currentObjectInfo.EndPositionRandomised = slider.EndPosition; @@ -190,34 +185,44 @@ namespace osu.Game.Rulesets.Osu.Mods } /// - /// Calculates the min. distances from the 's position to the playfield border for the slider to be fully inside of the playfield. + /// Calculates the bounding box of a 's position for the slider to be fully inside of the playfield. /// - private MarginPadding getMinSliderMargin(Slider slider) + private RectangleF getSliderBoundingBox(Slider slider) { var pathPositions = new List(); slider.Path.GetPathToProgress(pathPositions, 0, 1); - var minMargin = new MarginPadding(); + var box = new RectangleF(); foreach (var pos in pathPositions) { - minMargin.Left = Math.Max(minMargin.Left, -pos.X); - minMargin.Right = Math.Max(minMargin.Right, pos.X); - minMargin.Top = Math.Max(minMargin.Top, -pos.Y); - minMargin.Bottom = Math.Max(minMargin.Bottom, pos.Y); + box.X = Math.Max(box.X, -pos.X); + box.Y = Math.Max(box.Y, -pos.Y); + box.Width = Math.Min(box.Width, OsuPlayfield.BASE_SIZE.X - pos.X - box.X); + box.Height = Math.Min(box.Height, OsuPlayfield.BASE_SIZE.Y - pos.Y - box.Y); } - minMargin.Left = Math.Min(minMargin.Left, OsuPlayfield.BASE_SIZE.X - minMargin.Right); - minMargin.Top = Math.Min(minMargin.Top, OsuPlayfield.BASE_SIZE.Y - minMargin.Bottom); - var radius = (float)slider.Radius; - minMargin.Left += radius; - minMargin.Right += radius; - minMargin.Top += radius; - minMargin.Bottom += radius; + box.X += radius; + box.Y += radius; + box.Width -= radius * 2; + box.Height -= radius * 2; - return minMargin; + // If the slider is larger than the playfield, force the slider to stay at its original position + if (box.Width < 0) + { + box.Width = 0; + box.X = slider.Position.X; + } + + if (box.Height < 0) + { + box.Height = 0; + box.Y = slider.Position.Y; + } + + return box; } /// From 328dcb4d6b76e546d20acbb2a46b4b2d2419e54f Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Thu, 1 Jul 2021 10:07:26 +0800 Subject: [PATCH 07/43] Use `Math.Clamp` instead of `MathHelper.Clamp` --- osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs index 71c070d91b..f4358118c7 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs @@ -243,8 +243,8 @@ namespace osu.Game.Rulesets.Osu.Mods private Vector2 clampToPlayfield(Vector2 position, float radius) { - position.X = MathHelper.Clamp(position.X, radius, OsuPlayfield.BASE_SIZE.X - radius); - position.Y = MathHelper.Clamp(position.Y, radius, OsuPlayfield.BASE_SIZE.Y - radius); + position.X = Math.Clamp(position.X, radius, OsuPlayfield.BASE_SIZE.X - radius); + position.Y = Math.Clamp(position.Y, radius, OsuPlayfield.BASE_SIZE.Y - radius); return position; } From 6e1839fcf2e48f90806bdebf3953d8152c3eab4f Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Thu, 1 Jul 2021 10:08:28 +0800 Subject: [PATCH 08/43] Rename `shift_object_count` to `objects_to_shift_before_slider` --- osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs index f4358118c7..844d8d76a1 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs @@ -29,7 +29,7 @@ namespace osu.Game.Rulesets.Osu.Mods /// /// Number of previous hit circles to be shifted together when a slider needs to be moved. /// - private const int shift_object_count = 10; + private const int objects_to_shift_before_slider = 10; private Random rng; @@ -84,7 +84,7 @@ namespace osu.Game.Rulesets.Osu.Mods { var toBeShifted = new List(); - for (int j = i - 1; j >= i - shift_object_count && j >= 0; j--) + for (int j = i - 1; j >= i - objects_to_shift_before_slider && j >= 0; j--) { // only shift hit circles if (!(hitObjects[j] is HitCircle)) break; From 7585f1f79088b63e30cebc866757c7e248da5040 Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Thu, 1 Jul 2021 10:59:06 +0800 Subject: [PATCH 09/43] Move special case handling back to `moveSliderIntoPlayfield` --- osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs | 31 +++++++++------------- 1 file changed, 12 insertions(+), 19 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs index 844d8d76a1..ede929bfc4 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs @@ -148,14 +148,20 @@ namespace osu.Game.Rulesets.Osu.Mods /// The that this slider has been shifted by. private Vector2 moveSliderIntoPlayfield(Slider slider, RandomObjectInfo currentObjectInfo) { - var minMargin = getSliderBoundingBox(slider); + var boundingBox = getSliderBoundingBox(slider); var prevPosition = slider.Position; - slider.Position = new Vector2( - Math.Clamp(slider.Position.X, minMargin.Left, minMargin.Right), - Math.Clamp(slider.Position.Y, minMargin.Top, minMargin.Bottom) - ); + // If the slider is larger than the playfield, force it to stay at the original position + var newX = boundingBox.Width < 0 + ? currentObjectInfo.PositionOriginal.X + : Math.Clamp(slider.Position.X, boundingBox.Left, boundingBox.Right); + + var newY = boundingBox.Height < 0 + ? currentObjectInfo.PositionOriginal.Y + : Math.Clamp(slider.Position.Y, boundingBox.Top, boundingBox.Bottom); + + slider.Position = new Vector2(newX, newY); currentObjectInfo.PositionRandomised = slider.Position; currentObjectInfo.EndPositionRandomised = slider.EndPosition; @@ -192,7 +198,7 @@ namespace osu.Game.Rulesets.Osu.Mods var pathPositions = new List(); slider.Path.GetPathToProgress(pathPositions, 0, 1); - var box = new RectangleF(); + var box = new RectangleF(Vector2.Zero, OsuPlayfield.BASE_SIZE); foreach (var pos in pathPositions) { @@ -209,19 +215,6 @@ namespace osu.Game.Rulesets.Osu.Mods box.Width -= radius * 2; box.Height -= radius * 2; - // If the slider is larger than the playfield, force the slider to stay at its original position - if (box.Width < 0) - { - box.Width = 0; - box.X = slider.Position.X; - } - - if (box.Height < 0) - { - box.Height = 0; - box.Y = slider.Position.Y; - } - return box; } From c69455cfd019d47e67f22538c2f9a83a5f0713b6 Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Thu, 1 Jul 2021 11:20:55 +0800 Subject: [PATCH 10/43] Fixed slider bounding box calculation --- osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs index ede929bfc4..abc7db0a82 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs @@ -202,8 +202,16 @@ namespace osu.Game.Rulesets.Osu.Mods foreach (var pos in pathPositions) { + // Reduce Width and Height accordingly after increasing X and Y + // to keep the right and bottom edge of the rectangle in place + var right = box.Right; box.X = Math.Max(box.X, -pos.X); + box.Width = right - box.X; + + var bottom = box.Bottom; box.Y = Math.Max(box.Y, -pos.Y); + box.Height = bottom - box.Y; + box.Width = Math.Min(box.Width, OsuPlayfield.BASE_SIZE.X - pos.X - box.X); box.Height = Math.Min(box.Height, OsuPlayfield.BASE_SIZE.Y - pos.Y - box.Y); } From eecf4af0293384a6fe3c0da24eaf322529ba7951 Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Mon, 5 Jul 2021 09:16:01 +0800 Subject: [PATCH 11/43] Rename `getSliderBoundingBox` and add comments --- osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs index abc7db0a82..4c856ef4a0 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs @@ -148,18 +148,19 @@ namespace osu.Game.Rulesets.Osu.Mods /// The that this slider has been shifted by. private Vector2 moveSliderIntoPlayfield(Slider slider, RandomObjectInfo currentObjectInfo) { - var boundingBox = getSliderBoundingBox(slider); + var area = getSliderPlacementArea(slider); var prevPosition = slider.Position; + // Clamp slider position to the placement area // If the slider is larger than the playfield, force it to stay at the original position - var newX = boundingBox.Width < 0 + var newX = area.Width < 0 ? currentObjectInfo.PositionOriginal.X - : Math.Clamp(slider.Position.X, boundingBox.Left, boundingBox.Right); + : Math.Clamp(slider.Position.X, area.Left, area.Right); - var newY = boundingBox.Height < 0 + var newY = area.Height < 0 ? currentObjectInfo.PositionOriginal.Y - : Math.Clamp(slider.Position.Y, boundingBox.Top, boundingBox.Bottom); + : Math.Clamp(slider.Position.Y, area.Top, area.Bottom); slider.Position = new Vector2(newX, newY); @@ -191,15 +192,21 @@ namespace osu.Game.Rulesets.Osu.Mods } /// - /// Calculates the bounding box of a 's position for the slider to be fully inside of the playfield. + /// Calculates a that includes all possible positions of the slider such that + /// the entire slider is inside the playfield. /// - private RectangleF getSliderBoundingBox(Slider slider) + /// + /// If the slider is larger than the playfield, the returned may have negative width/height. + /// + private RectangleF getSliderPlacementArea(Slider slider) { var pathPositions = new List(); slider.Path.GetPathToProgress(pathPositions, 0, 1); + // Initially, assume that the slider can be placed anywhere in the playfield var box = new RectangleF(Vector2.Zero, OsuPlayfield.BASE_SIZE); + // Then narrow down the area with each path position foreach (var pos in pathPositions) { // Reduce Width and Height accordingly after increasing X and Y @@ -216,6 +223,7 @@ namespace osu.Game.Rulesets.Osu.Mods box.Height = Math.Min(box.Height, OsuPlayfield.BASE_SIZE.Y - pos.Y - box.Y); } + // Reduce the area by slider radius, so that the slider fits inside the playfield completely var radius = (float)slider.Radius; box.X += radius; From e10b7867c158059e9f041edbdbc5ac10011f2203 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 10 Jul 2021 12:13:36 +0200 Subject: [PATCH 12/43] Rewrite method again to hopefully help readability --- osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs | 54 ++++++++++++---------- 1 file changed, 30 insertions(+), 24 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs index 4c856ef4a0..cd63b618bd 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs @@ -148,7 +148,7 @@ namespace osu.Game.Rulesets.Osu.Mods /// The that this slider has been shifted by. private Vector2 moveSliderIntoPlayfield(Slider slider, RandomObjectInfo currentObjectInfo) { - var area = getSliderPlacementArea(slider); + var area = calculatePossibleMovementBounds(slider); var prevPosition = slider.Position; @@ -192,46 +192,52 @@ namespace osu.Game.Rulesets.Osu.Mods } /// - /// Calculates a that includes all possible positions of the slider such that - /// the entire slider is inside the playfield. + /// Calculates a which contains all of the possible movements of the slider (in relative X/Y coordinates) + /// such that the entire slider is inside the playfield. /// /// /// If the slider is larger than the playfield, the returned may have negative width/height. /// - private RectangleF getSliderPlacementArea(Slider slider) + private RectangleF calculatePossibleMovementBounds(Slider slider) { var pathPositions = new List(); slider.Path.GetPathToProgress(pathPositions, 0, 1); - // Initially, assume that the slider can be placed anywhere in the playfield - var box = new RectangleF(Vector2.Zero, OsuPlayfield.BASE_SIZE); + float minX = float.PositiveInfinity; + float maxX = float.NegativeInfinity; - // Then narrow down the area with each path position + float minY = float.PositiveInfinity; + float maxY = float.NegativeInfinity; + + // Compute the bounding box of the slider. foreach (var pos in pathPositions) { - // Reduce Width and Height accordingly after increasing X and Y - // to keep the right and bottom edge of the rectangle in place - var right = box.Right; - box.X = Math.Max(box.X, -pos.X); - box.Width = right - box.X; + minX = MathF.Min(minX, pos.X); + maxX = MathF.Max(maxX, pos.X); - var bottom = box.Bottom; - box.Y = Math.Max(box.Y, -pos.Y); - box.Height = bottom - box.Y; - - box.Width = Math.Min(box.Width, OsuPlayfield.BASE_SIZE.X - pos.X - box.X); - box.Height = Math.Min(box.Height, OsuPlayfield.BASE_SIZE.Y - pos.Y - box.Y); + minY = MathF.Min(minY, pos.Y); + maxY = MathF.Max(maxY, pos.Y); } - // Reduce the area by slider radius, so that the slider fits inside the playfield completely + // Take the circle radius into account. var radius = (float)slider.Radius; - box.X += radius; - box.Y += radius; - box.Width -= radius * 2; - box.Height -= radius * 2; + minX -= radius; + minY -= radius; - return box; + maxX += radius; + maxY += radius; + + // Given the bounding box of the slider (via min/max X/Y), + // the amount that the slider can move to the left is minX (with the sign flipped, since positive X is to the right), + // and the amount that it can move to the right is WIDTH - maxX. + // Same calculation applies for the Y axis. + float left = -minX; + float right = OsuPlayfield.BASE_SIZE.X - maxX; + float top = -minY; + float bottom = OsuPlayfield.BASE_SIZE.Y - maxY; + + return new RectangleF(left, top, right - left, bottom - top); } /// From 5cd11a02baeea3cd5f5766dcaf8bf1cabb424d16 Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Rosfatiputra Date: Sat, 10 Jul 2021 17:56:37 +0700 Subject: [PATCH 13/43] add autolink test --- .../UserInterface/TestSceneOsuMarkdownContainer.cs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneOsuMarkdownContainer.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneOsuMarkdownContainer.cs index 931af7bc95..82e26cb87d 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneOsuMarkdownContainer.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneOsuMarkdownContainer.cs @@ -95,6 +95,15 @@ _**italic with underscore, bold with asterisk**_"; }); } + [Test] + public void TestAutoLink() + { + AddStep("Add autolink", () => + { + markdownContainer.Text = ""; + }); + } + [Test] public void TestInlineCode() { From 45ff28f83baae57d8a8ae4fc2f6c4f9f9ff04b2f Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Rosfatiputra Date: Sat, 10 Jul 2021 17:57:33 +0700 Subject: [PATCH 14/43] add autolink constructor --- .../Graphics/Containers/Markdown/OsuMarkdownLinkText.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/osu.Game/Graphics/Containers/Markdown/OsuMarkdownLinkText.cs b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownLinkText.cs index f91a0e40e3..82e556f653 100644 --- a/osu.Game/Graphics/Containers/Markdown/OsuMarkdownLinkText.cs +++ b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownLinkText.cs @@ -26,6 +26,12 @@ namespace osu.Game.Graphics.Containers.Markdown title = linkInline.Title; } + public OsuMarkdownLinkText(AutolinkInline autolinkInline) + : base(autolinkInline) + { + text = autolinkInline.Url; + } + [BackgroundDependencyLoader] private void load() { From e4f13e311ea7f36672ed9f8027c5ee597f3ca3ee Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Rosfatiputra Date: Sat, 10 Jul 2021 17:58:00 +0700 Subject: [PATCH 15/43] override add auto link in text flow container --- .../Containers/Markdown/OsuMarkdownTextFlowContainer.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Graphics/Containers/Markdown/OsuMarkdownTextFlowContainer.cs b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownTextFlowContainer.cs index 36b48b7769..a7cd6b3905 100644 --- a/osu.Game/Graphics/Containers/Markdown/OsuMarkdownTextFlowContainer.cs +++ b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownTextFlowContainer.cs @@ -17,6 +17,9 @@ namespace osu.Game.Graphics.Containers.Markdown protected override void AddLinkText(string text, LinkInline linkInline) => AddDrawable(new OsuMarkdownLinkText(text, linkInline)); + protected override void AddAutoLink(AutolinkInline autolinkInline) + => AddDrawable(new OsuMarkdownLinkText(autolinkInline)); + protected override void AddImage(LinkInline linkInline) => AddDrawable(new OsuMarkdownImage(linkInline)); // TODO : Change font to monospace From c181a724c6e903e61d43f23f5ff12f65ae183788 Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Sun, 11 Jul 2021 22:01:28 +0800 Subject: [PATCH 16/43] Refactor hit object clamping --- osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs | 99 +++++++++++++--------- 1 file changed, 59 insertions(+), 40 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs index cd63b618bd..e134dcef89 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs @@ -67,34 +67,32 @@ namespace osu.Game.Rulesets.Osu.Mods applyRandomisation(rateOfChangeMultiplier, previous, current); - // Move hit objects back into the playfield if they are outside of it, - // which would sometimes happen during big jumps otherwise. - current.PositionRandomised = clampToPlayfield(current.PositionRandomised, (float)hitObject.Radius); + // Move hit objects back into the playfield if they are outside of it + Vector2 shift = Vector2.Zero; - hitObject.Position = current.PositionRandomised; - - // update end position as it may have changed as a result of the position update. - current.EndPositionRandomised = current.PositionRandomised; - - if (hitObject is Slider slider) + if (hitObject is HitCircle circle) { - Vector2 shift = moveSliderIntoPlayfield(slider, current); + shift = clampHitCircleToPlayfield(circle, current); + } + else if (hitObject is Slider slider) + { + shift = clampSliderToPlayfield(slider, current); + } - if (shift != Vector2.Zero) + if (shift != Vector2.Zero) + { + var toBeShifted = new List(); + + for (int j = i - 1; j >= i - objects_to_shift_before_slider && j >= 0; j--) { - var toBeShifted = new List(); + // only shift hit circles + if (!(hitObjects[j] is HitCircle)) break; - for (int j = i - 1; j >= i - objects_to_shift_before_slider && j >= 0; j--) - { - // only shift hit circles - if (!(hitObjects[j] is HitCircle)) break; - - toBeShifted.Add(hitObjects[j]); - } - - if (toBeShifted.Count > 0) - applyDecreasingShift(toBeShifted, shift); + toBeShifted.Add(hitObjects[j]); } + + if (toBeShifted.Count > 0) + applyDecreasingShift(toBeShifted, shift); } previous = current; @@ -145,31 +143,29 @@ namespace osu.Game.Rulesets.Osu.Mods /// /// Moves the and all necessary nested s into the if they aren't already. /// - /// The that this slider has been shifted by. - private Vector2 moveSliderIntoPlayfield(Slider slider, RandomObjectInfo currentObjectInfo) + /// The deviation from the original randomised position in order to fit within the playfield. + private Vector2 clampSliderToPlayfield(Slider slider, RandomObjectInfo objectInfo) { var area = calculatePossibleMovementBounds(slider); - var prevPosition = slider.Position; + var previousPosition = objectInfo.PositionRandomised; // Clamp slider position to the placement area // If the slider is larger than the playfield, force it to stay at the original position var newX = area.Width < 0 - ? currentObjectInfo.PositionOriginal.X - : Math.Clamp(slider.Position.X, area.Left, area.Right); + ? objectInfo.PositionOriginal.X + : Math.Clamp(previousPosition.X, area.Left, area.Right); var newY = area.Height < 0 - ? currentObjectInfo.PositionOriginal.Y - : Math.Clamp(slider.Position.Y, area.Top, area.Bottom); + ? objectInfo.PositionOriginal.Y + : Math.Clamp(previousPosition.Y, area.Top, area.Bottom); - slider.Position = new Vector2(newX, newY); + slider.Position = objectInfo.PositionRandomised = new Vector2(newX, newY); + objectInfo.EndPositionRandomised = slider.EndPosition; - currentObjectInfo.PositionRandomised = slider.Position; - currentObjectInfo.EndPositionRandomised = slider.EndPosition; + shiftNestedObjects(slider, objectInfo.PositionRandomised - objectInfo.PositionOriginal); - shiftNestedObjects(slider, currentObjectInfo.PositionRandomised - currentObjectInfo.PositionOriginal); - - return slider.Position - prevPosition; + return objectInfo.PositionRandomised - previousPosition; } /// @@ -187,7 +183,7 @@ namespace osu.Game.Rulesets.Osu.Mods // The last object is shifted by a vector slightly larger than zero Vector2 position = hitObject.Position + shift * ((hitObjects.Count - i) / (float)(hitObjects.Count + 1)); - hitObject.Position = clampToPlayfield(position, (float)hitObject.Radius); + hitObject.Position = clampToPlayfieldWithPadding(position, (float)hitObject.Radius); } } @@ -256,12 +252,35 @@ namespace osu.Game.Rulesets.Osu.Mods } } - private Vector2 clampToPlayfield(Vector2 position, float radius) + /// + /// Move the randomised position of a hit circle so that it fits inside the playfield. + /// + /// The deviation from the original randomised position in order to fit within the playfield. + private Vector2 clampHitCircleToPlayfield(HitCircle circle, RandomObjectInfo objectInfo) { - position.X = Math.Clamp(position.X, radius, OsuPlayfield.BASE_SIZE.X - radius); - position.Y = Math.Clamp(position.Y, radius, OsuPlayfield.BASE_SIZE.Y - radius); + var previousPosition = objectInfo.PositionRandomised; + objectInfo.EndPositionRandomised = objectInfo.PositionRandomised = clampToPlayfieldWithPadding( + objectInfo.PositionRandomised, + (float)circle.Radius + ); - return position; + circle.Position = objectInfo.PositionRandomised; + + return objectInfo.PositionRandomised - previousPosition; + } + + /// + /// Clamp a position to playfield, keeping a specified distance from the edges. + /// + /// The position to be clamped. + /// The minimum distance allowed from playfield edges. + /// The clamped position. + private Vector2 clampToPlayfieldWithPadding(Vector2 position, float padding) + { + return new Vector2( + Math.Clamp(position.X, padding, OsuPlayfield.BASE_SIZE.X - padding), + Math.Clamp(position.Y, padding, OsuPlayfield.BASE_SIZE.Y - padding) + ); } private class RandomObjectInfo From 7aecafeecb26f72ca1d2e7a1d2f72b266a2b09a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 11 Jul 2021 16:46:30 +0200 Subject: [PATCH 17/43] Rename constant to reflect its purpose --- osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs index e134dcef89..4409017ac9 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs @@ -27,9 +27,9 @@ namespace osu.Game.Rulesets.Osu.Mods private static readonly float playfield_diagonal = OsuPlayfield.BASE_SIZE.LengthFast; /// - /// Number of previous hit circles to be shifted together when a slider needs to be moved. + /// Number of previous hitobjects to be shifted together when another object is being moved. /// - private const int objects_to_shift_before_slider = 10; + private const int preceding_hitobjects_to_shift = 10; private Random rng; @@ -83,7 +83,7 @@ namespace osu.Game.Rulesets.Osu.Mods { var toBeShifted = new List(); - for (int j = i - 1; j >= i - objects_to_shift_before_slider && j >= 0; j--) + for (int j = i - 1; j >= i - preceding_hitobjects_to_shift && j >= 0; j--) { // only shift hit circles if (!(hitObjects[j] is HitCircle)) break; From 63dedb36dec1612c5d68c0293d1d551fbecd57fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 11 Jul 2021 16:49:23 +0200 Subject: [PATCH 18/43] Rename variable --- osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs index 4409017ac9..f919ecf839 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs @@ -146,19 +146,19 @@ namespace osu.Game.Rulesets.Osu.Mods /// The deviation from the original randomised position in order to fit within the playfield. private Vector2 clampSliderToPlayfield(Slider slider, RandomObjectInfo objectInfo) { - var area = calculatePossibleMovementBounds(slider); + var possibleMovementBounds = calculatePossibleMovementBounds(slider); var previousPosition = objectInfo.PositionRandomised; // Clamp slider position to the placement area // If the slider is larger than the playfield, force it to stay at the original position - var newX = area.Width < 0 + var newX = possibleMovementBounds.Width < 0 ? objectInfo.PositionOriginal.X - : Math.Clamp(previousPosition.X, area.Left, area.Right); + : Math.Clamp(previousPosition.X, possibleMovementBounds.Left, possibleMovementBounds.Right); - var newY = area.Height < 0 + var newY = possibleMovementBounds.Height < 0 ? objectInfo.PositionOriginal.Y - : Math.Clamp(previousPosition.Y, area.Top, area.Bottom); + : Math.Clamp(previousPosition.Y, possibleMovementBounds.Top, possibleMovementBounds.Bottom); slider.Position = objectInfo.PositionRandomised = new Vector2(newX, newY); objectInfo.EndPositionRandomised = slider.EndPosition; From c6bd58ea4bf0cecfaa3456038ee29a928b801a46 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 12 Jul 2021 19:20:39 +0900 Subject: [PATCH 19/43] Exit match sub screen when a room goes away Closes #13847. I think we can probably get some test coverage of this if required, but needs a bit of thought (basically an error needs to be thrown during the multiplayer client portion of the join procedure, after `CurrentRoom` is non-null but before the join completes). Manual testing on password branch (#13861) is possible since it currently errors due to missing method on the live/dev servers. - Create game, which will fail with `MethodNotExists`. - Note the fields on the settings screen are emptied. - Fill fields again and press create game (crash). --- .../Multiplayer/MultiplayerMatchSubScreen.cs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs index 4b8c4422ec..4f5d54e203 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs @@ -48,6 +48,9 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer [Resolved] private OngoingOperationTracker ongoingOperationTracker { get; set; } + [Resolved] + private Bindable currentRoom { get; set; } + private MultiplayerMatchSettingsOverlay settingsOverlay; private readonly IBindable isConnected = new Bindable(); @@ -273,6 +276,16 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer if (!connected.NewValue) Schedule(this.Exit); }, true); + + currentRoom.BindValueChanged(room => + { + if (room.NewValue == null) + { + // the room has gone away. + // this could mean something happened during the join process, or an external connection issue occurred. + Schedule(this.Exit); + } + }, true); } protected override void UpdateMods() From 1deaefacb7c7236c20a5526db5ffb75a58bbc7d8 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 12 Jul 2021 17:53:15 +0300 Subject: [PATCH 20/43] Add "basic" lime colour theme --- osu.Game/Overlays/OverlayColourProvider.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/osu.Game/Overlays/OverlayColourProvider.cs b/osu.Game/Overlays/OverlayColourProvider.cs index abd1e43f25..4c698c2af7 100644 --- a/osu.Game/Overlays/OverlayColourProvider.cs +++ b/osu.Game/Overlays/OverlayColourProvider.cs @@ -14,6 +14,7 @@ namespace osu.Game.Overlays public static OverlayColourProvider Red { get; } = new OverlayColourProvider(OverlayColourScheme.Red); public static OverlayColourProvider Pink { get; } = new OverlayColourProvider(OverlayColourScheme.Pink); public static OverlayColourProvider Orange { get; } = new OverlayColourProvider(OverlayColourScheme.Orange); + public static OverlayColourProvider Lime { get; } = new OverlayColourProvider(OverlayColourScheme.Lime); public static OverlayColourProvider Green { get; } = new OverlayColourProvider(OverlayColourScheme.Green); public static OverlayColourProvider Purple { get; } = new OverlayColourProvider(OverlayColourScheme.Purple); public static OverlayColourProvider Blue { get; } = new OverlayColourProvider(OverlayColourScheme.Blue); @@ -68,6 +69,9 @@ namespace osu.Game.Overlays case OverlayColourScheme.Orange: return 46 / 360f; + case OverlayColourScheme.Lime: + return 90 / 360f; + case OverlayColourScheme.Green: return 115 / 360f; @@ -85,6 +89,7 @@ namespace osu.Game.Overlays Red, Pink, Orange, + Lime, Green, Purple, Blue From 9869986c59482f4c64b718e1bceb17d0ebc5d9aa Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 12 Jul 2021 17:55:29 +0300 Subject: [PATCH 21/43] Remove duplicated colour definitions --- osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs | 3 ++- osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs | 3 ++- osu.Game/Graphics/OsuColour.cs | 4 ---- osu.Game/Overlays/Wiki/Markdown/WikiNoticeContainer.cs | 5 ++--- 4 files changed, 6 insertions(+), 9 deletions(-) diff --git a/osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs index 8a6cfaf688..b871f8bcc7 100644 --- a/osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs +++ b/osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs @@ -3,6 +3,7 @@ using osu.Framework.Bindables; using osu.Game.Graphics; +using osu.Game.Overlays; using osuTK.Graphics; namespace osu.Game.Beatmaps.ControlPoints @@ -25,7 +26,7 @@ namespace osu.Game.Beatmaps.ControlPoints MaxValue = 10 }; - public override Color4 GetRepresentingColour(OsuColour colours) => colours.Lime1; + public override Color4 GetRepresentingColour(OsuColour colours) => OverlayColourProvider.Lime.Colour1; /// /// The speed multiplier at this control point. diff --git a/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs index ec20328fab..22808671b0 100644 --- a/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs +++ b/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs @@ -4,6 +4,7 @@ using osu.Framework.Bindables; using osu.Game.Beatmaps.Timing; using osu.Game.Graphics; +using osu.Game.Overlays; using osuTK.Graphics; namespace osu.Game.Beatmaps.ControlPoints @@ -20,7 +21,7 @@ namespace osu.Game.Beatmaps.ControlPoints /// private const double default_beat_length = 60000.0 / 60.0; - public override Color4 GetRepresentingColour(OsuColour colours) => colours.Orange1; + public override Color4 GetRepresentingColour(OsuColour colours) => OverlayColourProvider.Orange.Colour1; public static readonly TimingControlPoint DEFAULT = new TimingControlPoint { diff --git a/osu.Game/Graphics/OsuColour.cs b/osu.Game/Graphics/OsuColour.cs index a44c28eaa6..3533ab177c 100644 --- a/osu.Game/Graphics/OsuColour.cs +++ b/osu.Game/Graphics/OsuColour.cs @@ -198,10 +198,6 @@ namespace osu.Game.Graphics public readonly Color4 GrayE = Color4Extensions.FromHex(@"eee"); public readonly Color4 GrayF = Color4Extensions.FromHex(@"fff"); - // in latest editor design logic, need to figure out where these sit... - public readonly Color4 Lime1 = Color4Extensions.FromHex(@"b2ff66"); - public readonly Color4 Orange1 = Color4Extensions.FromHex(@"ffd966"); - // Content Background public readonly Color4 B5 = Color4Extensions.FromHex(@"222a28"); diff --git a/osu.Game/Overlays/Wiki/Markdown/WikiNoticeContainer.cs b/osu.Game/Overlays/Wiki/Markdown/WikiNoticeContainer.cs index 421806eea8..91d6460ea6 100644 --- a/osu.Game/Overlays/Wiki/Markdown/WikiNoticeContainer.cs +++ b/osu.Game/Overlays/Wiki/Markdown/WikiNoticeContainer.cs @@ -7,7 +7,6 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers.Markdown; using osu.Framework.Graphics.Shapes; -using osu.Game.Graphics; namespace osu.Game.Overlays.Wiki.Markdown { @@ -66,7 +65,7 @@ namespace osu.Game.Overlays.Wiki.Markdown public string Text { get; set; } [BackgroundDependencyLoader] - private void load(OverlayColourProvider colourProvider, OsuColour colour) + private void load(OverlayColourProvider colourProvider) { RelativeSizeAxes = Axes.X; AutoSizeAxes = Axes.Y; @@ -81,7 +80,7 @@ namespace osu.Game.Overlays.Wiki.Markdown }, textFlow = parentFlowComponent.CreateTextFlow().With(t => { - t.Colour = colour.Orange1; + t.Colour = OverlayColourProvider.Orange.Colour1; t.Padding = new MarginPadding { Vertical = 10, From 143777271164b912107d949b1727324347ebf609 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 12 Jul 2021 18:11:51 +0300 Subject: [PATCH 22/43] Update hue of orange colour scheme --- osu.Game/Overlays/OverlayColourProvider.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/OverlayColourProvider.cs b/osu.Game/Overlays/OverlayColourProvider.cs index abd1e43f25..1f78ffc870 100644 --- a/osu.Game/Overlays/OverlayColourProvider.cs +++ b/osu.Game/Overlays/OverlayColourProvider.cs @@ -66,7 +66,7 @@ namespace osu.Game.Overlays return 333 / 360f; case OverlayColourScheme.Orange: - return 46 / 360f; + return 45 / 360f; case OverlayColourScheme.Green: return 115 / 360f; From c96a76df673e46320147e6ee6b7fe0886df2bc13 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 12 Jul 2021 18:17:13 +0300 Subject: [PATCH 23/43] Update specified link --- osu.Game/Overlays/OverlayColourProvider.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/OverlayColourProvider.cs b/osu.Game/Overlays/OverlayColourProvider.cs index 1f78ffc870..ac1cfb15c7 100644 --- a/osu.Game/Overlays/OverlayColourProvider.cs +++ b/osu.Game/Overlays/OverlayColourProvider.cs @@ -51,7 +51,7 @@ namespace osu.Game.Overlays private Color4 getColour(float saturation, float lightness) => Color4.FromHsl(new Vector4(getBaseHue(colourScheme), saturation, lightness, 1)); - // See https://github.com/ppy/osu-web/blob/4218c288292d7c810b619075471eaea8bbb8f9d8/app/helpers.php#L1463 + // See https://github.com/ppy/osu-web/blob/5a536d217a21582aad999db50a981003d3ad5659/app/helpers.php#L1620-L1628 private static float getBaseHue(OverlayColourScheme colourScheme) { switch (colourScheme) From b2b966463a87a3e441f8cf18acea6bb3abe07e23 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 12 Jul 2021 19:05:36 +0300 Subject: [PATCH 24/43] Update hue of green colour scheme --- osu.Game/Overlays/OverlayColourProvider.cs | 23 +++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/osu.Game/Overlays/OverlayColourProvider.cs b/osu.Game/Overlays/OverlayColourProvider.cs index ac1cfb15c7..8ef3ccbf80 100644 --- a/osu.Game/Overlays/OverlayColourProvider.cs +++ b/osu.Game/Overlays/OverlayColourProvider.cs @@ -12,11 +12,12 @@ namespace osu.Game.Overlays private readonly OverlayColourScheme colourScheme; public static OverlayColourProvider Red { get; } = new OverlayColourProvider(OverlayColourScheme.Red); - public static OverlayColourProvider Pink { get; } = new OverlayColourProvider(OverlayColourScheme.Pink); + public static OverlayColourProvider DarkOrange { get; } = new OverlayColourProvider(OverlayColourScheme.DarkOrange); public static OverlayColourProvider Orange { get; } = new OverlayColourProvider(OverlayColourScheme.Orange); public static OverlayColourProvider Green { get; } = new OverlayColourProvider(OverlayColourScheme.Green); - public static OverlayColourProvider Purple { get; } = new OverlayColourProvider(OverlayColourScheme.Purple); public static OverlayColourProvider Blue { get; } = new OverlayColourProvider(OverlayColourScheme.Blue); + public static OverlayColourProvider Purple { get; } = new OverlayColourProvider(OverlayColourScheme.Purple); + public static OverlayColourProvider Pink { get; } = new OverlayColourProvider(OverlayColourScheme.Pink); public OverlayColourProvider(OverlayColourScheme colourScheme) { @@ -62,8 +63,8 @@ namespace osu.Game.Overlays case OverlayColourScheme.Red: return 0; - case OverlayColourScheme.Pink: - return 333 / 360f; + case OverlayColourScheme.DarkOrange: + return 20 / 360f; case OverlayColourScheme.Orange: return 45 / 360f; @@ -71,11 +72,17 @@ namespace osu.Game.Overlays case OverlayColourScheme.Green: return 115 / 360f; - case OverlayColourScheme.Purple: - return 255 / 360f; + case OverlayColourScheme.Cyan: + return 160 / 360f; case OverlayColourScheme.Blue: return 200 / 360f; + + case OverlayColourScheme.Purple: + return 255 / 360f; + + case OverlayColourScheme.Pink: + return 333 / 360f; } } } @@ -84,9 +91,11 @@ namespace osu.Game.Overlays { Red, Pink, + DarkOrange, Orange, Green, Purple, - Blue + Blue, + Cyan, } } From 62a00a82de0c184ca525ae639594f26e74d7e18a Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 12 Jul 2021 19:11:14 +0300 Subject: [PATCH 25/43] Revert completely irrelevant changes This reverts commit b2b966463a87a3e441f8cf18acea6bb3abe07e23. --- osu.Game/Overlays/OverlayColourProvider.cs | 23 +++++++--------------- 1 file changed, 7 insertions(+), 16 deletions(-) diff --git a/osu.Game/Overlays/OverlayColourProvider.cs b/osu.Game/Overlays/OverlayColourProvider.cs index 8ef3ccbf80..ac1cfb15c7 100644 --- a/osu.Game/Overlays/OverlayColourProvider.cs +++ b/osu.Game/Overlays/OverlayColourProvider.cs @@ -12,12 +12,11 @@ namespace osu.Game.Overlays private readonly OverlayColourScheme colourScheme; public static OverlayColourProvider Red { get; } = new OverlayColourProvider(OverlayColourScheme.Red); - public static OverlayColourProvider DarkOrange { get; } = new OverlayColourProvider(OverlayColourScheme.DarkOrange); + public static OverlayColourProvider Pink { get; } = new OverlayColourProvider(OverlayColourScheme.Pink); public static OverlayColourProvider Orange { get; } = new OverlayColourProvider(OverlayColourScheme.Orange); public static OverlayColourProvider Green { get; } = new OverlayColourProvider(OverlayColourScheme.Green); - public static OverlayColourProvider Blue { get; } = new OverlayColourProvider(OverlayColourScheme.Blue); public static OverlayColourProvider Purple { get; } = new OverlayColourProvider(OverlayColourScheme.Purple); - public static OverlayColourProvider Pink { get; } = new OverlayColourProvider(OverlayColourScheme.Pink); + public static OverlayColourProvider Blue { get; } = new OverlayColourProvider(OverlayColourScheme.Blue); public OverlayColourProvider(OverlayColourScheme colourScheme) { @@ -63,8 +62,8 @@ namespace osu.Game.Overlays case OverlayColourScheme.Red: return 0; - case OverlayColourScheme.DarkOrange: - return 20 / 360f; + case OverlayColourScheme.Pink: + return 333 / 360f; case OverlayColourScheme.Orange: return 45 / 360f; @@ -72,17 +71,11 @@ namespace osu.Game.Overlays case OverlayColourScheme.Green: return 115 / 360f; - case OverlayColourScheme.Cyan: - return 160 / 360f; - - case OverlayColourScheme.Blue: - return 200 / 360f; - case OverlayColourScheme.Purple: return 255 / 360f; - case OverlayColourScheme.Pink: - return 333 / 360f; + case OverlayColourScheme.Blue: + return 200 / 360f; } } } @@ -91,11 +84,9 @@ namespace osu.Game.Overlays { Red, Pink, - DarkOrange, Orange, Green, Purple, - Blue, - Cyan, + Blue } } From 821c1b5335d5d3a7b8bd3bd89b3a30f255bc320e Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 12 Jul 2021 19:11:26 +0300 Subject: [PATCH 26/43] Update hue of green colour scheme --- osu.Game/Overlays/OverlayColourProvider.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/OverlayColourProvider.cs b/osu.Game/Overlays/OverlayColourProvider.cs index ac1cfb15c7..3fb8c6c4cf 100644 --- a/osu.Game/Overlays/OverlayColourProvider.cs +++ b/osu.Game/Overlays/OverlayColourProvider.cs @@ -69,7 +69,7 @@ namespace osu.Game.Overlays return 45 / 360f; case OverlayColourScheme.Green: - return 115 / 360f; + return 125 / 360f; case OverlayColourScheme.Purple: return 255 / 360f; From 5cffaf4d3b591c366209228afd17822b06c73881 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 13 Jul 2021 14:34:56 +0900 Subject: [PATCH 27/43] Add extra explanatory comment to avoid any confusion --- .../Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs index 4f5d54e203..aa8a59e13b 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs @@ -283,6 +283,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer { // the room has gone away. // this could mean something happened during the join process, or an external connection issue occurred. + // one specific scenario is where the underlying room is created, but the signalr server returns an error during the join process. this triggers a PartRoom operation (see https://github.com/ppy/osu/blob/7654df94f6f37b8382be7dfcb4f674e03bd35427/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerRoomManager.cs#L97) Schedule(this.Exit); } }, true); From 60e17fc2b7b81998f4e2d5c1f30cdb26dc67b4d2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 13 Jul 2021 17:12:35 +0900 Subject: [PATCH 28/43] Fix disconnected-from-server multiplayer exit sequence being blocked by confirmation dialog --- .../OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs index 4b8c4422ec..e1eebb7e41 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs @@ -310,7 +310,9 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer public override bool OnExiting(IScreen next) { - if (client.Room == null) + // the room may not be left immediately after a disconnection due to async flow, + // so checking the IsConnected status is also required. + if (client.Room == null || !client.IsConnected.Value) { // room has not been created yet; exit immediately. return base.OnExiting(next); From 28ff92e34ec214f97d70d546ef7c8650ce757f1d Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 13 Jul 2021 17:31:28 +0900 Subject: [PATCH 29/43] Add test --- .../Multiplayer/TestSceneMultiplayer.cs | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs index 2bb3129f68..7673efb78f 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs @@ -21,6 +21,7 @@ using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Mods; using osu.Game.Screens; using osu.Game.Screens.OnlinePlay.Components; +using osu.Game.Screens.OnlinePlay.Lounge; using osu.Game.Screens.OnlinePlay.Match.Components; using osu.Game.Screens.OnlinePlay.Multiplayer; using osu.Game.Screens.OnlinePlay.Multiplayer.Match; @@ -184,6 +185,26 @@ namespace osu.Game.Tests.Visual.Multiplayer AddUntilStep("play started", () => !multiplayerScreen.IsCurrentScreen()); } + [Test] + public void TestSubScreenExitedWhenDisconnectedFromMultiplayerServer() + { + createRoom(() => new Room + { + Name = { Value = "Test Room" }, + Playlist = + { + new PlaylistItem + { + Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.RulesetID == 0)).BeatmapInfo }, + Ruleset = { Value = new OsuRuleset().RulesetInfo }, + } + } + }); + + AddStep("disconnect", () => client.Disconnect()); + AddUntilStep("back in lounge", () => this.ChildrenOfType().FirstOrDefault()?.IsCurrentScreen() == true); + } + [Test] public void TestLeaveNavigation() { From 6da2a3d51fe441280af9536bca3fcf02662826ce Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Tue, 13 Jul 2021 10:50:11 +0200 Subject: [PATCH 30/43] Add zero-length objects check and tests --- .../Checks/CheckZeroLengthObjectsTest.cs | 94 +++++++++++++++++++ .../Edit/Checks/CheckZeroLengthObjects.cs | 47 ++++++++++ 2 files changed, 141 insertions(+) create mode 100644 osu.Game.Tests/Editing/Checks/CheckZeroLengthObjectsTest.cs create mode 100644 osu.Game/Rulesets/Edit/Checks/CheckZeroLengthObjects.cs diff --git a/osu.Game.Tests/Editing/Checks/CheckZeroLengthObjectsTest.cs b/osu.Game.Tests/Editing/Checks/CheckZeroLengthObjectsTest.cs new file mode 100644 index 0000000000..93b20cd166 --- /dev/null +++ b/osu.Game.Tests/Editing/Checks/CheckZeroLengthObjectsTest.cs @@ -0,0 +1,94 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using System.Linq; +using Moq; +using NUnit.Framework; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Edit.Checks; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Types; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Tests.Beatmaps; +using osuTK; + +namespace osu.Game.Tests.Editing.Checks +{ + [TestFixture] + public class CheckZeroLengthObjectsTest + { + private CheckZeroLengthObjects check; + + [SetUp] + public void Setup() + { + check = new CheckZeroLengthObjects(); + } + + [Test] + public void TestCircle() + { + assertOk(new List + { + new HitCircle { StartTime = 1000, Position = new Vector2(0, 0) } + }); + } + + [Test] + public void TestRegularSlider() + { + assertOk(new List + { + getSliderMock(1000).Object + }); + } + + [Test] + public void TestZeroLengthSlider() + { + assertZeroLength(new List + { + getSliderMock(0).Object + }); + } + + [Test] + public void TestNegativeLengthSlider() + { + assertZeroLength(new List + { + getSliderMock(-1000).Object + }); + } + + private Mock getSliderMock(double duration) + { + var mockSlider = new Mock(); + mockSlider.As().Setup(d => d.Duration).Returns(duration); + + return mockSlider; + } + + private void assertOk(List hitObjects) + { + Assert.That(check.Run(getContext(hitObjects)), Is.Empty); + } + + private void assertZeroLength(List hitObjects) + { + var issues = check.Run(getContext(hitObjects)).ToList(); + + Assert.That(issues, Has.Count.EqualTo(1)); + Assert.That(issues.First().Template is CheckZeroLengthObjects.IssueTemplateZeroLength); + } + + private BeatmapVerifierContext getContext(List hitObjects) + { + var beatmap = new Beatmap { HitObjects = hitObjects }; + + return new BeatmapVerifierContext(beatmap, new TestWorkingBeatmap(beatmap)); + } + } +} diff --git a/osu.Game/Rulesets/Edit/Checks/CheckZeroLengthObjects.cs b/osu.Game/Rulesets/Edit/Checks/CheckZeroLengthObjects.cs new file mode 100644 index 0000000000..b9be94736b --- /dev/null +++ b/osu.Game/Rulesets/Edit/Checks/CheckZeroLengthObjects.cs @@ -0,0 +1,47 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using osu.Game.Rulesets.Edit.Checks.Components; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Types; + +namespace osu.Game.Rulesets.Edit.Checks +{ + public class CheckZeroLengthObjects : ICheck + { + /// + /// The duration can be this low before being treated as having no length, in case of precision errors. Unit is milliseconds. + /// + private const double leniency = 0.5d; + + public CheckMetadata Metadata { get; } = new CheckMetadata(CheckCategory.Compose, "Zero-length hitobjects"); + + public IEnumerable PossibleTemplates => new IssueTemplate[] + { + new IssueTemplateZeroLength(this) + }; + + public IEnumerable Run(BeatmapVerifierContext context) + { + foreach (var hitObject in context.Beatmap.HitObjects) + { + if (!(hitObject is IHasDuration hasDuration)) + continue; + + if (hasDuration.Duration < leniency) + yield return new IssueTemplateZeroLength(this).Create(hitObject, hasDuration.Duration); + } + } + + public class IssueTemplateZeroLength : IssueTemplate + { + public IssueTemplateZeroLength(ICheck check) + : base(check, IssueType.Problem, "{0} has a duration of {1:0}.") + { + } + + public Issue Create(HitObject hitobject, double duration) => new Issue(hitobject, this, hitobject.GetType(), duration); + } + } +} From fec944830144eefcd2913a0d435ca507959a719f Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Tue, 13 Jul 2021 10:50:41 +0200 Subject: [PATCH 31/43] Add too short sliders check and tests --- .../Editor/Checks/CheckTooShortSlidersTest.cs | 145 ++++++++++++++++++ .../Edit/Checks/CheckTooShortSliders.cs | 48 ++++++ 2 files changed, 193 insertions(+) create mode 100644 osu.Game.Rulesets.Osu.Tests/Editor/Checks/CheckTooShortSlidersTest.cs create mode 100644 osu.Game.Rulesets.Osu/Edit/Checks/CheckTooShortSliders.cs diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/Checks/CheckTooShortSlidersTest.cs b/osu.Game.Rulesets.Osu.Tests/Editor/Checks/CheckTooShortSlidersTest.cs new file mode 100644 index 0000000000..2eab5a4ce6 --- /dev/null +++ b/osu.Game.Rulesets.Osu.Tests/Editor/Checks/CheckTooShortSlidersTest.cs @@ -0,0 +1,145 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using System.Linq; +using NUnit.Framework; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Osu.Edit.Checks; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Tests.Beatmaps; +using osuTK; + +namespace osu.Game.Rulesets.Osu.Tests.Editor.Checks +{ + [TestFixture] + public class CheckTooShortSlidersTest + { + private CheckTooShortSliders check; + + [SetUp] + public void Setup() + { + check = new CheckTooShortSliders(); + } + + [Test] + public void TestLongSlider() + { + Slider slider = new Slider + { + StartTime = 0, + RepeatCount = 0, + Path = new SliderPath(new[] + { + new PathControlPoint(new Vector2(0, 0)), + new PathControlPoint(new Vector2(100, 0)) + }) + }; + + slider.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); + + assertOk(new List { slider }); + } + + [Test] + public void TestShortSlider() + { + Slider slider = new Slider + { + StartTime = 0, + RepeatCount = 0, + Path = new SliderPath(new[] + { + new PathControlPoint(new Vector2(0, 0)), + new PathControlPoint(new Vector2(25, 0)) + }) + }; + + slider.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); + + assertOk(new List { slider }); + } + + [Test] + public void TestTooShortSliderExpert() + { + Slider slider = new Slider + { + StartTime = 0, + RepeatCount = 0, + Path = new SliderPath(new[] + { + new PathControlPoint(new Vector2(0, 0)), + new PathControlPoint(new Vector2(10, 0)) + }) + }; + + slider.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); + + assertOk(new List { slider }, DifficultyRating.Expert); + } + + [Test] + public void TestTooShortSlider() + { + Slider slider = new Slider + { + StartTime = 0, + RepeatCount = 0, + Path = new SliderPath(new[] + { + new PathControlPoint(new Vector2(0, 0)), + new PathControlPoint(new Vector2(10, 0)) + }) + }; + + slider.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); + + assertTooShort(new List { slider }); + } + + [Test] + public void TestTooShortSliderWithRepeats() + { + // Would be ok if we looked at the duration, but not if we look at the span duration. + Slider slider = new Slider + { + StartTime = 0, + RepeatCount = 2, + Path = new SliderPath(new[] + { + new PathControlPoint(new Vector2(0, 0)), + new PathControlPoint(new Vector2(10, 0)) + }) + }; + + slider.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); + + assertTooShort(new List { slider }); + } + + private void assertOk(List hitObjects, DifficultyRating difficultyRating = DifficultyRating.Easy) + { + Assert.That(check.Run(getContext(hitObjects, difficultyRating)), Is.Empty); + } + + private void assertTooShort(List hitObjects, DifficultyRating difficultyRating = DifficultyRating.Easy) + { + var issues = check.Run(getContext(hitObjects, difficultyRating)).ToList(); + + Assert.That(issues, Has.Count.EqualTo(1)); + Assert.That(issues.First().Template is CheckTooShortSliders.IssueTemplateTooShort); + } + + private BeatmapVerifierContext getContext(List hitObjects, DifficultyRating difficultyRating) + { + var beatmap = new Beatmap { HitObjects = hitObjects }; + + return new BeatmapVerifierContext(beatmap, new TestWorkingBeatmap(beatmap), difficultyRating); + } + } +} diff --git a/osu.Game.Rulesets.Osu/Edit/Checks/CheckTooShortSliders.cs b/osu.Game.Rulesets.Osu/Edit/Checks/CheckTooShortSliders.cs new file mode 100644 index 0000000000..159498c479 --- /dev/null +++ b/osu.Game.Rulesets.Osu/Edit/Checks/CheckTooShortSliders.cs @@ -0,0 +1,48 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Edit.Checks.Components; +using osu.Game.Rulesets.Osu.Objects; + +namespace osu.Game.Rulesets.Osu.Edit.Checks +{ + public class CheckTooShortSliders : ICheck + { + /// + /// The shortest acceptable duration between the head and tail of the slider (so ignoring repeats). + /// + private const double span_duration_threshold = 125; // 240 BPM 1/2 + + public CheckMetadata Metadata { get; } = new CheckMetadata(CheckCategory.Spread, "Too short sliders"); + + public IEnumerable PossibleTemplates => new IssueTemplate[] + { + new IssueTemplateTooShort(this) + }; + + public IEnumerable Run(BeatmapVerifierContext context) + { + if (context.InterpretedDifficulty > DifficultyRating.Easy) + yield break; + + foreach (var hitObject in context.Beatmap.HitObjects) + { + if (hitObject is Slider slider && slider.SpanDuration < span_duration_threshold) + yield return new IssueTemplateTooShort(this).Create(slider); + } + } + + public class IssueTemplateTooShort : IssueTemplate + { + public IssueTemplateTooShort(ICheck check) + : base(check, IssueType.Problem, "This slider is too short ({0:0} ms), expected at least {1:0} ms.") + { + } + + public Issue Create(Slider slider) => new Issue(slider, this, slider.SpanDuration, span_duration_threshold); + } + } +} From 53c0298b5ea9689eba4eb33dfc29f43224e63e50 Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Tue, 13 Jul 2021 10:51:40 +0200 Subject: [PATCH 32/43] Add too short spinners check and tests --- .../Checks/CheckTooShortSpinnersTest.cs | 116 ++++++++++++++++++ .../Edit/Checks/CheckTooShortSpinners.cs | 61 +++++++++ 2 files changed, 177 insertions(+) create mode 100644 osu.Game.Rulesets.Osu.Tests/Editor/Checks/CheckTooShortSpinnersTest.cs create mode 100644 osu.Game.Rulesets.Osu/Edit/Checks/CheckTooShortSpinners.cs diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/Checks/CheckTooShortSpinnersTest.cs b/osu.Game.Rulesets.Osu.Tests/Editor/Checks/CheckTooShortSpinnersTest.cs new file mode 100644 index 0000000000..6a3f168ee1 --- /dev/null +++ b/osu.Game.Rulesets.Osu.Tests/Editor/Checks/CheckTooShortSpinnersTest.cs @@ -0,0 +1,116 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using System.Linq; +using NUnit.Framework; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Osu.Edit.Checks; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Tests.Beatmaps; + +namespace osu.Game.Rulesets.Osu.Tests.Editor.Checks +{ + [TestFixture] + public class CheckTooShortSpinnersTest + { + private CheckTooShortSpinners check; + private BeatmapDifficulty difficulty; + + [SetUp] + public void Setup() + { + check = new CheckTooShortSpinners(); + difficulty = new BeatmapDifficulty(); + } + + [Test] + public void TestLongSpinner() + { + Spinner spinner = new Spinner { StartTime = 0, Duration = 4000 }; + spinner.ApplyDefaults(new ControlPointInfo(), difficulty); + + assertOk(new List { spinner }, difficulty); + } + + [Test] + public void TestShortSpinner() + { + Spinner spinner = new Spinner { StartTime = 0, Duration = 750 }; + spinner.ApplyDefaults(new ControlPointInfo(), difficulty); + + assertOk(new List { spinner }, difficulty); + } + + [Test] + public void TestVeryShortSpinner() + { + // Spinners at a certain duration only get 1000 points if approached by auto at a certain angle, making it difficult to determine. + Spinner spinner = new Spinner { StartTime = 0, Duration = 475 }; + spinner.ApplyDefaults(new ControlPointInfo(), difficulty); + + assertVeryShort(new List { spinner }, difficulty); + } + + [Test] + public void TestTooShortSpinner() + { + Spinner spinner = new Spinner { StartTime = 0, Duration = 400 }; + spinner.ApplyDefaults(new ControlPointInfo(), difficulty); + + assertTooShort(new List { spinner }, difficulty); + } + + [Test] + public void TestTooShortSpinnerVaryingOd() + { + const double duration = 450; + + var difficultyLowOd = new BeatmapDifficulty { OverallDifficulty = 1 }; + Spinner spinnerLowOd = new Spinner { StartTime = 0, Duration = duration }; + spinnerLowOd.ApplyDefaults(new ControlPointInfo(), difficultyLowOd); + + var difficultyHighOd = new BeatmapDifficulty { OverallDifficulty = 10 }; + Spinner spinnerHighOd = new Spinner { StartTime = 0, Duration = duration }; + spinnerHighOd.ApplyDefaults(new ControlPointInfo(), difficultyHighOd); + + assertOk(new List { spinnerLowOd }, difficultyLowOd); + assertTooShort(new List { spinnerHighOd }, difficultyHighOd); + } + + private void assertOk(List hitObjects, BeatmapDifficulty beatmapDifficulty) + { + Assert.That(check.Run(getContext(hitObjects, beatmapDifficulty)), Is.Empty); + } + + private void assertVeryShort(List hitObjects, BeatmapDifficulty beatmapDifficulty) + { + var issues = check.Run(getContext(hitObjects, beatmapDifficulty)).ToList(); + + Assert.That(issues, Has.Count.EqualTo(1)); + Assert.That(issues.First().Template is CheckTooShortSpinners.IssueTemplateVeryShort); + } + + private void assertTooShort(List hitObjects, BeatmapDifficulty beatmapDifficulty) + { + var issues = check.Run(getContext(hitObjects, beatmapDifficulty)).ToList(); + + Assert.That(issues, Has.Count.EqualTo(1)); + Assert.That(issues.First().Template is CheckTooShortSpinners.IssueTemplateTooShort); + } + + private BeatmapVerifierContext getContext(List hitObjects, BeatmapDifficulty beatmapDifficulty) + { + var beatmap = new Beatmap + { + HitObjects = hitObjects, + BeatmapInfo = new BeatmapInfo { BaseDifficulty = beatmapDifficulty } + }; + + return new BeatmapVerifierContext(beatmap, new TestWorkingBeatmap(beatmap)); + } + } +} diff --git a/osu.Game.Rulesets.Osu/Edit/Checks/CheckTooShortSpinners.cs b/osu.Game.Rulesets.Osu/Edit/Checks/CheckTooShortSpinners.cs new file mode 100644 index 0000000000..0d0c3d9e69 --- /dev/null +++ b/osu.Game.Rulesets.Osu/Edit/Checks/CheckTooShortSpinners.cs @@ -0,0 +1,61 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Edit.Checks.Components; +using osu.Game.Rulesets.Osu.Objects; + +namespace osu.Game.Rulesets.Osu.Edit.Checks +{ + public class CheckTooShortSpinners : ICheck + { + public CheckMetadata Metadata { get; } = new CheckMetadata(CheckCategory.Spread, "Too short spinners"); + + public IEnumerable PossibleTemplates => new IssueTemplate[] + { + new IssueTemplateTooShort(this) + }; + + public IEnumerable Run(BeatmapVerifierContext context) + { + double od = context.Beatmap.BeatmapInfo.BaseDifficulty.OverallDifficulty; + + // These are meant to reflect the duration necessary for auto to score at least 1000 points on the spinner. + // It's difficult to eliminate warnings here, as auto achieving 1000 points depends on the approach angle on some spinners. + double warningThreshold = 500 + (od < 5 ? (5 - od) * -21.8 : (od - 5) * 20); // Anything above this is always ok. + double problemThreshold = 450 + (od < 5 ? (5 - od) * -17 : (od - 5) * 17); // Anything below this is never ok. + + foreach (var hitObject in context.Beatmap.HitObjects) + { + if (!(hitObject is Spinner spinner)) + continue; + + if (spinner.Duration < problemThreshold) + yield return new IssueTemplateTooShort(this).Create(spinner); + else if (spinner.Duration < warningThreshold) + yield return new IssueTemplateVeryShort(this).Create(spinner); + } + } + + public class IssueTemplateTooShort : IssueTemplate + { + public IssueTemplateTooShort(ICheck check) + : base(check, IssueType.Problem, "This spinner is too short. Auto cannot achieve 1000 points on this.") + { + } + + public Issue Create(Spinner spinner) => new Issue(spinner, this); + } + + public class IssueTemplateVeryShort : IssueTemplate + { + public IssueTemplateVeryShort(ICheck check) + : base(check, IssueType.Warning, "This spinner may be too short. Ensure auto can achieve 1000 points on this.") + { + } + + public Issue Create(Spinner spinner) => new Issue(spinner, this); + } + } +} From 3a5912e35eb03736be9cc6ca6883fa4617583e5c Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Tue, 13 Jul 2021 10:53:25 +0200 Subject: [PATCH 33/43] Add new checks to verifiers --- osu.Game.Rulesets.Osu/Edit/OsuBeatmapVerifier.cs | 4 +++- osu.Game/Rulesets/Edit/BeatmapVerifier.cs | 3 ++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuBeatmapVerifier.cs b/osu.Game.Rulesets.Osu/Edit/OsuBeatmapVerifier.cs index 896e904f3f..221723e4cd 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuBeatmapVerifier.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuBeatmapVerifier.cs @@ -15,10 +15,12 @@ namespace osu.Game.Rulesets.Osu.Edit { // Compose new CheckOffscreenObjects(), + new CheckTooShortSpinners(), // Spread new CheckTimeDistanceEquality(), - new CheckLowDiffOverlaps() + new CheckLowDiffOverlaps(), + new CheckTooShortSliders(), }; public IEnumerable Run(BeatmapVerifierContext context) diff --git a/osu.Game/Rulesets/Edit/BeatmapVerifier.cs b/osu.Game/Rulesets/Edit/BeatmapVerifier.cs index 706eec226c..81f4808789 100644 --- a/osu.Game/Rulesets/Edit/BeatmapVerifier.cs +++ b/osu.Game/Rulesets/Edit/BeatmapVerifier.cs @@ -27,7 +27,8 @@ namespace osu.Game.Rulesets.Edit // Compose new CheckUnsnappedObjects(), - new CheckConcurrentObjects() + new CheckConcurrentObjects(), + new CheckZeroLengthObjects(), }; public IEnumerable Run(BeatmapVerifierContext context) From e791669c40559122375e050642839aa363352af8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 13 Jul 2021 17:59:35 +0900 Subject: [PATCH 34/43] Fix multiplayer screen buttons showing no text when local user not available --- .../Match/MultiplayerReadyButton.cs | 20 +++++++------------ .../Match/MultiplayerSpectateButton.cs | 14 ++++--------- 2 files changed, 11 insertions(+), 23 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerReadyButton.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerReadyButton.cs index f2dd9a6f25..baf9570209 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerReadyButton.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerReadyButton.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Diagnostics; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Audio; @@ -72,25 +71,20 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match { var localUser = Client.LocalUser; - if (localUser == null) - return; + int newCountReady = Room?.Users.Count(u => u.State == MultiplayerUserState.Ready) ?? 0; + int newCountTotal = Room?.Users.Count(u => u.State != MultiplayerUserState.Spectating) ?? 0; - Debug.Assert(Room != null); - - int newCountReady = Room.Users.Count(u => u.State == MultiplayerUserState.Ready); - int newCountTotal = Room.Users.Count(u => u.State != MultiplayerUserState.Spectating); - - string countText = $"({newCountReady} / {newCountTotal} ready)"; - - switch (localUser.State) + switch (localUser?.State) { - case MultiplayerUserState.Idle: + default: button.Text = "Ready"; updateButtonColour(true); break; case MultiplayerUserState.Spectating: case MultiplayerUserState.Ready: + string countText = $"({newCountReady} / {newCountTotal} ready)"; + if (Room?.Host?.Equals(localUser) == true) { button.Text = $"Start match {countText}"; @@ -108,7 +102,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match bool enableButton = Client.Room?.State == MultiplayerRoomState.Open && !operationInProgress.Value; // When the local user is the host and spectating the match, the "start match" state should be enabled if any users are ready. - if (localUser.State == MultiplayerUserState.Spectating) + if (localUser?.State == MultiplayerUserState.Spectating) enableButton &= Room?.Host?.Equals(localUser) == true && newCountReady > 0; button.Enabled.Value = enableButton; diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerSpectateButton.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerSpectateButton.cs index 04150902bc..db99c6a5d5 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerSpectateButton.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerSpectateButton.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Diagnostics; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -57,14 +56,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match private void updateState() { - var localUser = Client.LocalUser; - - if (localUser == null) - return; - - Debug.Assert(Room != null); - - switch (localUser.State) + switch (Client.LocalUser?.State) { default: button.Text = "Spectate"; @@ -81,7 +73,9 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match break; } - button.Enabled.Value = Client.Room?.State != MultiplayerRoomState.Closed && !operationInProgress.Value; + button.Enabled.Value = Client.Room != null + && Client.Room.State != MultiplayerRoomState.Closed + && !operationInProgress.Value; } private class ButtonWithTrianglesExposed : TriangleButton From 6b663037e47b0b448d98449134248699dfce9d6d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 13 Jul 2021 19:37:02 +0900 Subject: [PATCH 35/43] Use `switch` for pattern matching --- osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs index f919ecf839..7794ef8072 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs @@ -70,13 +70,15 @@ namespace osu.Game.Rulesets.Osu.Mods // Move hit objects back into the playfield if they are outside of it Vector2 shift = Vector2.Zero; - if (hitObject is HitCircle circle) + switch (hitObject) { - shift = clampHitCircleToPlayfield(circle, current); - } - else if (hitObject is Slider slider) - { - shift = clampSliderToPlayfield(slider, current); + case HitCircle circle: + shift = clampHitCircleToPlayfield(circle, current); + break; + + case Slider slider: + shift = clampSliderToPlayfield(slider, current); + break; } if (shift != Vector2.Zero) From 4314946e10726756eb7a2df717fbbab131a14e19 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 13 Jul 2021 19:37:17 +0900 Subject: [PATCH 36/43] Reorganise functions to order more logically (hitcircle before slider methods) --- osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs | 34 +++++++++++----------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs index 7794ef8072..1a2e5d92b4 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs @@ -142,6 +142,23 @@ namespace osu.Game.Rulesets.Osu.Mods current.PositionRandomised = previous.EndPositionRandomised + posRelativeToPrev; } + /// + /// Move the randomised position of a hit circle so that it fits inside the playfield. + /// + /// The deviation from the original randomised position in order to fit within the playfield. + private Vector2 clampHitCircleToPlayfield(HitCircle circle, RandomObjectInfo objectInfo) + { + var previousPosition = objectInfo.PositionRandomised; + objectInfo.EndPositionRandomised = objectInfo.PositionRandomised = clampToPlayfieldWithPadding( + objectInfo.PositionRandomised, + (float)circle.Radius + ); + + circle.Position = objectInfo.PositionRandomised; + + return objectInfo.PositionRandomised - previousPosition; + } + /// /// Moves the and all necessary nested s into the if they aren't already. /// @@ -254,23 +271,6 @@ namespace osu.Game.Rulesets.Osu.Mods } } - /// - /// Move the randomised position of a hit circle so that it fits inside the playfield. - /// - /// The deviation from the original randomised position in order to fit within the playfield. - private Vector2 clampHitCircleToPlayfield(HitCircle circle, RandomObjectInfo objectInfo) - { - var previousPosition = objectInfo.PositionRandomised; - objectInfo.EndPositionRandomised = objectInfo.PositionRandomised = clampToPlayfieldWithPadding( - objectInfo.PositionRandomised, - (float)circle.Radius - ); - - circle.Position = objectInfo.PositionRandomised; - - return objectInfo.PositionRandomised - previousPosition; - } - /// /// Clamp a position to playfield, keeping a specified distance from the edges. /// From 9bec53bfa8922bc7de66278a1f0c30e1ba65eb00 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 20 Jun 2021 19:31:24 +0200 Subject: [PATCH 37/43] Implement osu!-side popover --- .../UserInterface/TestSceneOsuPopover.cs | 103 ++++++++++++++++++ .../Graphics/UserInterfaceV2/OsuPopover.cs | 44 ++++++++ 2 files changed, 147 insertions(+) create mode 100644 osu.Game.Tests/Visual/UserInterface/TestSceneOsuPopover.cs create mode 100644 osu.Game/Graphics/UserInterfaceV2/OsuPopover.cs diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneOsuPopover.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneOsuPopover.cs new file mode 100644 index 0000000000..1848cf6a5e --- /dev/null +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneOsuPopover.cs @@ -0,0 +1,103 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Cursor; +using osu.Framework.Graphics.UserInterface; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterface; +using osu.Game.Graphics.UserInterfaceV2; +using osu.Game.Overlays; +using osuTK; + +namespace osu.Game.Tests.Visual.UserInterface +{ + public class TestSceneOsuPopover : OsuGridTestScene + { + public TestSceneOsuPopover() + : base(1, 2) + { + Cell(0, 0).Child = new PopoverContainer + { + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + new OsuSpriteText + { + Text = @"No OverlayColourProvider", + Font = OsuFont.Default.With(size: 40) + }, + new TriangleButtonWithPopover() + } + }; + + Cell(0, 1).Child = new ColourProvidingContainer(OverlayColourScheme.Orange) + { + RelativeSizeAxes = Axes.Both, + Child = new PopoverContainer + { + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + new OsuSpriteText + { + Text = @"With OverlayColourProvider (orange)", + Font = OsuFont.Default.With(size: 40) + }, + new TriangleButtonWithPopover() + } + } + }; + } + + private class TriangleButtonWithPopover : TriangleButton, IHasPopover + { + public TriangleButtonWithPopover() + { + Width = 100; + Height = 30; + Anchor = Anchor.Centre; + Origin = Anchor.Centre; + Text = @"open"; + Action = this.ShowPopover; + } + + public Popover GetPopover() => new OsuPopover + { + Child = new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Vertical, + Spacing = new Vector2(10), + Children = new Drawable[] + { + new OsuSpriteText + { + Text = @"sample text" + }, + new OsuTextBox + { + Width = 150, + Height = 30 + } + } + } + }; + } + + private class ColourProvidingContainer : Container + { + [Cached] + private OverlayColourProvider provider { get; } + + public ColourProvidingContainer(OverlayColourScheme colourScheme) + { + provider = new OverlayColourProvider(colourScheme); + } + } + } +} diff --git a/osu.Game/Graphics/UserInterfaceV2/OsuPopover.cs b/osu.Game/Graphics/UserInterfaceV2/OsuPopover.cs new file mode 100644 index 0000000000..33165ea8b5 --- /dev/null +++ b/osu.Game/Graphics/UserInterfaceV2/OsuPopover.cs @@ -0,0 +1,44 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using JetBrains.Annotations; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Effects; +using osu.Framework.Graphics.UserInterface; +using osu.Game.Overlays; +using osuTK; + +namespace osu.Game.Graphics.UserInterfaceV2 +{ + public class OsuPopover : Popover + { + private const float fade_duration = 250; + + public OsuPopover(bool withPadding = true) + { + Content.Padding = withPadding ? new MarginPadding(20) : new MarginPadding(); + Body.Masking = true; + Body.CornerRadius = 10; + Body.Margin = new MarginPadding(10); + Body.EdgeEffect = new EdgeEffectParameters + { + Type = EdgeEffectType.Shadow, + Offset = new Vector2(0, 2), + Radius = 5, + Colour = Colour4.Black.Opacity(0.3f) + }; + } + + [BackgroundDependencyLoader(true)] + private void load([CanBeNull] OverlayColourProvider colourProvider, OsuColour osuColour) + { + Background.Colour = Arrow.Colour = colourProvider?.Background4 ?? osuColour.GreySeafoamDarker; + } + + protected override Drawable CreateArrow() => Empty(); + + protected override void PopIn() => this.FadeIn(fade_duration, Easing.OutQuint); + protected override void PopOut() => this.FadeOut(fade_duration, Easing.OutQuint); + } +} From b4961cd12e096e4068beb857844bc5bded30eb11 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 14 Jul 2021 03:18:13 +0300 Subject: [PATCH 38/43] Revert "Remove duplicated colour definitions" This reverts commit 9869986c59482f4c64b718e1bceb17d0ebc5d9aa. --- osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs | 3 +-- osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs | 3 +-- osu.Game/Graphics/OsuColour.cs | 4 ++++ osu.Game/Overlays/Wiki/Markdown/WikiNoticeContainer.cs | 5 +++-- 4 files changed, 9 insertions(+), 6 deletions(-) diff --git a/osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs index b871f8bcc7..8a6cfaf688 100644 --- a/osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs +++ b/osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs @@ -3,7 +3,6 @@ using osu.Framework.Bindables; using osu.Game.Graphics; -using osu.Game.Overlays; using osuTK.Graphics; namespace osu.Game.Beatmaps.ControlPoints @@ -26,7 +25,7 @@ namespace osu.Game.Beatmaps.ControlPoints MaxValue = 10 }; - public override Color4 GetRepresentingColour(OsuColour colours) => OverlayColourProvider.Lime.Colour1; + public override Color4 GetRepresentingColour(OsuColour colours) => colours.Lime1; /// /// The speed multiplier at this control point. diff --git a/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs index 22808671b0..ec20328fab 100644 --- a/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs +++ b/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs @@ -4,7 +4,6 @@ using osu.Framework.Bindables; using osu.Game.Beatmaps.Timing; using osu.Game.Graphics; -using osu.Game.Overlays; using osuTK.Graphics; namespace osu.Game.Beatmaps.ControlPoints @@ -21,7 +20,7 @@ namespace osu.Game.Beatmaps.ControlPoints /// private const double default_beat_length = 60000.0 / 60.0; - public override Color4 GetRepresentingColour(OsuColour colours) => OverlayColourProvider.Orange.Colour1; + public override Color4 GetRepresentingColour(OsuColour colours) => colours.Orange1; public static readonly TimingControlPoint DEFAULT = new TimingControlPoint { diff --git a/osu.Game/Graphics/OsuColour.cs b/osu.Game/Graphics/OsuColour.cs index 3533ab177c..a44c28eaa6 100644 --- a/osu.Game/Graphics/OsuColour.cs +++ b/osu.Game/Graphics/OsuColour.cs @@ -198,6 +198,10 @@ namespace osu.Game.Graphics public readonly Color4 GrayE = Color4Extensions.FromHex(@"eee"); public readonly Color4 GrayF = Color4Extensions.FromHex(@"fff"); + // in latest editor design logic, need to figure out where these sit... + public readonly Color4 Lime1 = Color4Extensions.FromHex(@"b2ff66"); + public readonly Color4 Orange1 = Color4Extensions.FromHex(@"ffd966"); + // Content Background public readonly Color4 B5 = Color4Extensions.FromHex(@"222a28"); diff --git a/osu.Game/Overlays/Wiki/Markdown/WikiNoticeContainer.cs b/osu.Game/Overlays/Wiki/Markdown/WikiNoticeContainer.cs index 91d6460ea6..421806eea8 100644 --- a/osu.Game/Overlays/Wiki/Markdown/WikiNoticeContainer.cs +++ b/osu.Game/Overlays/Wiki/Markdown/WikiNoticeContainer.cs @@ -7,6 +7,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers.Markdown; using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics; namespace osu.Game.Overlays.Wiki.Markdown { @@ -65,7 +66,7 @@ namespace osu.Game.Overlays.Wiki.Markdown public string Text { get; set; } [BackgroundDependencyLoader] - private void load(OverlayColourProvider colourProvider) + private void load(OverlayColourProvider colourProvider, OsuColour colour) { RelativeSizeAxes = Axes.X; AutoSizeAxes = Axes.Y; @@ -80,7 +81,7 @@ namespace osu.Game.Overlays.Wiki.Markdown }, textFlow = parentFlowComponent.CreateTextFlow().With(t => { - t.Colour = OverlayColourProvider.Orange.Colour1; + t.Colour = colour.Orange1; t.Padding = new MarginPadding { Vertical = 10, From 80636be7678cbc001dc0b02e59aac84fbd80c573 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 14 Jul 2021 03:22:00 +0300 Subject: [PATCH 39/43] Link `Lime1` and `Orange1` to their `OverlayColourProvider`'s alternative --- osu.Game/Graphics/OsuColour.cs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/osu.Game/Graphics/OsuColour.cs b/osu.Game/Graphics/OsuColour.cs index a44c28eaa6..c0bc8fdb76 100644 --- a/osu.Game/Graphics/OsuColour.cs +++ b/osu.Game/Graphics/OsuColour.cs @@ -3,6 +3,7 @@ using osu.Framework.Extensions.Color4Extensions; using osu.Game.Beatmaps; +using osu.Game.Overlays; using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; using osuTK.Graphics; @@ -198,8 +199,14 @@ namespace osu.Game.Graphics public readonly Color4 GrayE = Color4Extensions.FromHex(@"eee"); public readonly Color4 GrayF = Color4Extensions.FromHex(@"fff"); - // in latest editor design logic, need to figure out where these sit... + /// + /// Equivalent to 's . + /// public readonly Color4 Lime1 = Color4Extensions.FromHex(@"b2ff66"); + + /// + /// Equivalent to 's . + /// public readonly Color4 Orange1 = Color4Extensions.FromHex(@"ffd966"); // Content Background From ed296462911591152d0ec2f2fe558ab6edc62adb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 14 Jul 2021 12:32:14 +0900 Subject: [PATCH 40/43] Remove `IApplicableToDifficulty.ReadFromDifficulty` This was added specifically for `ModDifficultyAdjust`, but turned out to be more of a headache than we expected. We have since removed usage and would hope that this is not required by any other mods. Opting for complete removal rather than obsoletion, as we discovered this was already broken in multiple cases, with fixes being quite logically complex. If you happen to be a ruleset developer relying on this, open an issue and we'll talk you through a better approach (or check what `ModDifficultyAdjust` is doing now for an example). --- osu.Game/OsuGame.cs | 18 ------------------ .../Rulesets/Mods/IApplicableToDifficulty.cs | 7 ------- 2 files changed, 25 deletions(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index c25b520892..8119df43ac 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -525,16 +525,11 @@ namespace osu.Game private void beatmapChanged(ValueChangedEvent beatmap) { beatmap.OldValue?.CancelAsyncLoad(); - - updateModDefaults(); - beatmap.NewValue?.BeginAsyncLoad(); } private void modsChanged(ValueChangedEvent> mods) { - updateModDefaults(); - // a lease may be taken on the mods bindable, at which point we can't really ensure valid mods. if (SelectedMods.Disabled) return; @@ -546,19 +541,6 @@ namespace osu.Game } } - private void updateModDefaults() - { - BeatmapDifficulty baseDifficulty = Beatmap.Value.BeatmapInfo.BaseDifficulty; - - if (baseDifficulty != null && SelectedMods.Value.Any(m => m is IApplicableToDifficulty)) - { - var adjustedDifficulty = baseDifficulty.Clone(); - - foreach (var mod in SelectedMods.Value.OfType()) - mod.ReadFromDifficulty(adjustedDifficulty); - } - } - #endregion private PerformFromMenuRunner performFromMainMenuTask; diff --git a/osu.Game/Rulesets/Mods/IApplicableToDifficulty.cs b/osu.Game/Rulesets/Mods/IApplicableToDifficulty.cs index 34198da722..42b520ab26 100644 --- a/osu.Game/Rulesets/Mods/IApplicableToDifficulty.cs +++ b/osu.Game/Rulesets/Mods/IApplicableToDifficulty.cs @@ -10,13 +10,6 @@ namespace osu.Game.Rulesets.Mods /// public interface IApplicableToDifficulty : IApplicableMod { - /// - /// Called when a beatmap is changed. Can be used to read default values. - /// Any changes made will not be preserved. - /// - /// The difficulty to read from. - void ReadFromDifficulty(BeatmapDifficulty difficulty); - /// /// Called post beatmap conversion. Can be used to apply changes to difficulty attributes. /// From fdfd82aec42256ca1f44ab3b576c5c410640316d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 14 Jul 2021 23:23:44 +0900 Subject: [PATCH 41/43] Add elastic scale on appear --- osu.Game/Graphics/UserInterfaceV2/OsuPopover.cs | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/osu.Game/Graphics/UserInterfaceV2/OsuPopover.cs b/osu.Game/Graphics/UserInterfaceV2/OsuPopover.cs index 33165ea8b5..fff18e615f 100644 --- a/osu.Game/Graphics/UserInterfaceV2/OsuPopover.cs +++ b/osu.Game/Graphics/UserInterfaceV2/OsuPopover.cs @@ -14,10 +14,12 @@ namespace osu.Game.Graphics.UserInterfaceV2 public class OsuPopover : Popover { private const float fade_duration = 250; + private const double scale_duration = 500; public OsuPopover(bool withPadding = true) { Content.Padding = withPadding ? new MarginPadding(20) : new MarginPadding(); + Body.Masking = true; Body.CornerRadius = 10; Body.Margin = new MarginPadding(10); @@ -38,7 +40,16 @@ namespace osu.Game.Graphics.UserInterfaceV2 protected override Drawable CreateArrow() => Empty(); - protected override void PopIn() => this.FadeIn(fade_duration, Easing.OutQuint); - protected override void PopOut() => this.FadeOut(fade_duration, Easing.OutQuint); + protected override void PopIn() + { + this.ScaleTo(1, scale_duration, Easing.OutElasticHalf); + this.FadeIn(fade_duration, Easing.OutQuint); + } + + protected override void PopOut() + { + this.ScaleTo(0.7f, scale_duration, Easing.OutQuint); + this.FadeOut(fade_duration, Easing.OutQuint); + } } } From c6116676ebb9e090a82717537e09027399009a9c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 14 Jul 2021 23:23:48 +0900 Subject: [PATCH 42/43] Update framework --- osu.Android.props | 2 +- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index cd57d7478e..171a0862a1 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -52,7 +52,7 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index eb7a0141c7..b2f1d6507f 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -36,7 +36,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/osu.iOS.props b/osu.iOS.props index 2e5fab758d..dc15df6ea6 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -70,7 +70,7 @@ - + @@ -93,7 +93,7 @@ - + From 339fab75a83a56df8124e5794170d9caaf7976bf Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 14 Jul 2021 23:27:03 +0900 Subject: [PATCH 43/43] Rename colour variable in line with other usages --- osu.Game/Graphics/UserInterfaceV2/OsuPopover.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Graphics/UserInterfaceV2/OsuPopover.cs b/osu.Game/Graphics/UserInterfaceV2/OsuPopover.cs index fff18e615f..c07a5de1e4 100644 --- a/osu.Game/Graphics/UserInterfaceV2/OsuPopover.cs +++ b/osu.Game/Graphics/UserInterfaceV2/OsuPopover.cs @@ -33,9 +33,9 @@ namespace osu.Game.Graphics.UserInterfaceV2 } [BackgroundDependencyLoader(true)] - private void load([CanBeNull] OverlayColourProvider colourProvider, OsuColour osuColour) + private void load([CanBeNull] OverlayColourProvider colourProvider, OsuColour colours) { - Background.Colour = Arrow.Colour = colourProvider?.Background4 ?? osuColour.GreySeafoamDarker; + Background.Colour = Arrow.Colour = colourProvider?.Background4 ?? colours.GreySeafoamDarker; } protected override Drawable CreateArrow() => Empty();