From e67d9b1c2153c068a762820b866372818a2c5405 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 24 Nov 2021 12:14:52 +0900 Subject: [PATCH 001/146] Reorder members a bit --- .../Preprocessing/OsuDifficultyHitObject.cs | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs index d073d751d0..5c163eeb76 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs @@ -18,6 +18,11 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing protected new OsuHitObject BaseObject => (OsuHitObject)base.BaseObject; + /// + /// Milliseconds elapsed since the start time of the previous , with a minimum of 25ms. + /// + public readonly double StrainTime; + /// /// Normalized distance from the end position of the previous to the start position of this . /// @@ -28,31 +33,26 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing /// public double MovementDistance { get; private set; } - /// - /// Normalized distance between the start and end position of the previous . - /// - public double TravelDistance { get; private set; } - - /// - /// Angle the player has to take to hit this . - /// Calculated as the angle between the circles (current-2, current-1, current). - /// - public double? Angle { get; private set; } - /// /// Milliseconds elapsed since the end time of the previous , with a minimum of 25ms. /// public double MovementTime { get; private set; } + /// + /// Normalized distance between the start and end position of the previous . + /// + public double TravelDistance { get; private set; } + /// /// Milliseconds elapsed since the start time of the previous to the end time of the same previous , with a minimum of 25ms. /// public double TravelTime { get; private set; } /// - /// Milliseconds elapsed since the start time of the previous , with a minimum of 25ms. + /// Angle the player has to take to hit this . + /// Calculated as the angle between the circles (current-2, current-1, current). /// - public readonly double StrainTime; + public double? Angle { get; private set; } private readonly OsuHitObject lastLastObject; private readonly OsuHitObject lastObject; From 402de754f7f459fc48fd0d86a88c5f708a03a2c9 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 24 Nov 2021 12:37:16 +0900 Subject: [PATCH 002/146] Make TravelDistance/TravelTime apply to the current object --- .../Preprocessing/OsuDifficultyHitObject.cs | 27 ++++++++++--------- .../Difficulty/Skills/Aim.cs | 12 ++++----- .../Difficulty/Skills/Speed.cs | 3 ++- 3 files changed, 23 insertions(+), 19 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs index 5c163eeb76..eb36e96995 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs @@ -39,12 +39,12 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing public double MovementTime { get; private set; } /// - /// Normalized distance between the start and end position of the previous . + /// Normalized distance between the start and end position of this . /// public double TravelDistance { get; private set; } /// - /// Milliseconds elapsed since the start time of the previous to the end time of the same previous , with a minimum of 25ms. + /// Milliseconds elapsed between the start and end time of this , with a minimum of 25ms. /// public double TravelTime { get; private set; } @@ -84,15 +84,23 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing scalingFactor *= 1 + smallCircleBonus; } + if (BaseObject is Slider currentSlider) + { + computeSliderCursorPosition(currentSlider); + TravelDistance = currentSlider.LazyTravelDistance; + TravelTime = Math.Max(currentSlider.LazyTravelTime / clockRate, min_delta_time); + } + Vector2 lastCursorPosition = getEndCursorPosition(lastObject); + JumpDistance = (BaseObject.StackedPosition * scalingFactor - lastCursorPosition * scalingFactor).Length; + MovementTime = StrainTime; + MovementDistance = JumpDistance; if (lastObject is Slider lastSlider) { - computeSliderCursorPosition(lastSlider); - TravelDistance = lastSlider.LazyTravelDistance; - TravelTime = Math.Max(lastSlider.LazyTravelTime / clockRate, min_delta_time); - MovementTime = Math.Max(StrainTime - TravelTime, min_delta_time); + double lastTravelTime = Math.Max(lastSlider.LazyTravelTime / clockRate, min_delta_time); + MovementTime = Math.Max(MovementTime - lastTravelTime, min_delta_time); // Jump distance from the slider tail to the next object, as opposed to the lazy position of JumpDistance. float tailJumpDistance = Vector2.Subtract(lastSlider.TailCircle.StackedPosition, BaseObject.StackedPosition).Length * scalingFactor; @@ -102,12 +110,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing // In such cases, a leniency is applied by also considering the jump distance from the tail of the slider, and taking the minimum jump distance. // Additional distance is removed based on position of jump relative to slider follow circle radius. // JumpDistance is the leniency distance beyond the assumed_slider_radius. tailJumpDistance is maximum_slider_radius since the full distance of radial leniency is still possible. - MovementDistance = Math.Max(0, Math.Min(JumpDistance - (maximum_slider_radius - assumed_slider_radius), tailJumpDistance - maximum_slider_radius)); - } - else - { - MovementTime = StrainTime; - MovementDistance = JumpDistance; + MovementDistance = Math.Max(0, Math.Min(MovementDistance - (maximum_slider_radius - assumed_slider_radius), tailJumpDistance - maximum_slider_radius)); } if (lastLastObject != null && !(lastLastObject is Spinner)) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs index 2a8d2ce759..4ddcbb7770 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs @@ -50,7 +50,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills if (osuLastObj.BaseObject is Slider && withSliders) { double movementVelocity = osuCurrObj.MovementDistance / osuCurrObj.MovementTime; // calculate the movement velocity from slider end to current object - double travelVelocity = osuCurrObj.TravelDistance / osuCurrObj.TravelTime; // calculate the slider velocity from slider head to slider end. + double travelVelocity = osuLastObj.TravelDistance / osuLastObj.TravelTime; // calculate the slider velocity from slider head to slider end. currVelocity = Math.Max(currVelocity, movementVelocity + travelVelocity); // take the larger total combined velocity. } @@ -61,7 +61,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills if (osuLastLastObj.BaseObject is Slider && withSliders) { double movementVelocity = osuLastObj.MovementDistance / osuLastObj.MovementTime; - double travelVelocity = osuLastObj.TravelDistance / osuLastObj.TravelTime; + double travelVelocity = osuLastLastObj.TravelDistance / osuLastLastObj.TravelTime; prevVelocity = Math.Max(prevVelocity, movementVelocity + travelVelocity); } @@ -107,8 +107,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills if (Math.Max(prevVelocity, currVelocity) != 0) { // We want to use the average velocity over the whole object when awarding differences, not the individual jump and slider path velocities. - prevVelocity = (osuLastObj.JumpDistance + osuLastObj.TravelDistance) / osuLastObj.StrainTime; - currVelocity = (osuCurrObj.JumpDistance + osuCurrObj.TravelDistance) / osuCurrObj.StrainTime; + prevVelocity = (osuLastObj.JumpDistance + osuLastLastObj.TravelDistance) / osuLastObj.StrainTime; + currVelocity = (osuCurrObj.JumpDistance + osuLastObj.TravelDistance) / osuCurrObj.StrainTime; // Scale with ratio of difference compared to 0.5 * max dist. double distRatio = Math.Pow(Math.Sin(Math.PI / 2 * Math.Abs(prevVelocity - currVelocity) / Math.Max(prevVelocity, currVelocity)), 2); @@ -128,10 +128,10 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills velocityChangeBonus *= Math.Pow(Math.Min(osuCurrObj.StrainTime, osuLastObj.StrainTime) / Math.Max(osuCurrObj.StrainTime, osuLastObj.StrainTime), 2); } - if (osuCurrObj.TravelTime != 0) + if (osuLastObj.TravelTime != 0) { // Reward sliders based on velocity. - sliderBonus = osuCurrObj.TravelDistance / osuCurrObj.TravelTime; + sliderBonus = osuLastObj.TravelDistance / osuLastObj.TravelTime; } // Add in acute angle bonus or wide angle bonus + velocity change bonus, whichever is larger. diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs index 24881d9c47..b53d287ee6 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs @@ -154,7 +154,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills if (strainTime < min_speed_bonus) speedBonus = 1 + 0.75 * Math.Pow((min_speed_bonus - strainTime) / speed_balancing_factor, 2); - double distance = Math.Min(single_spacing_threshold, osuCurrObj.TravelDistance + osuCurrObj.JumpDistance); + double travelDistance = osuPrevObj?.TravelDistance ?? 0; + double distance = Math.Min(single_spacing_threshold, travelDistance + osuCurrObj.JumpDistance); return (speedBonus + speedBonus * Math.Pow(distance / single_spacing_threshold, 3.5)) / strainTime; } From e07c44d79a02028f0d944b51e86a053b3940e307 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 24 Nov 2021 13:01:15 +0900 Subject: [PATCH 003/146] Reword comment with a more diagrammatical explanation --- .../Preprocessing/OsuDifficultyHitObject.cs | 25 +++++++++++++------ 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs index eb36e96995..ecba2500d0 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs @@ -102,14 +102,25 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing double lastTravelTime = Math.Max(lastSlider.LazyTravelTime / clockRate, min_delta_time); MovementTime = Math.Max(MovementTime - lastTravelTime, min_delta_time); - // Jump distance from the slider tail to the next object, as opposed to the lazy position of JumpDistance. - float tailJumpDistance = Vector2.Subtract(lastSlider.TailCircle.StackedPosition, BaseObject.StackedPosition).Length * scalingFactor; + // + // We'll try to better approximate the real movements a player will take in patterns following on from sliders. Consider the following slider-to-object patterns: + // + // 1. <======o==> + // | / + // o + // + // 2. <======o==>---o + // |______| + // + // Where "<==>" represents a slider, and "o" represents where the cursor needs to be for either hitobject (for a slider, this is the lazy cursor position). + // + // Case (1) is an anti-flow pattern, where players will cut the slider short in order to move to the next object. The jump pattern is (o--o). + // Case (2) is a flow pattern, where players will follow the slider through to its visual extent. The jump pattern is (>--o). + // + // A lenience is applied by assuming that the player jumps the minimum of these two distances in all cases. + // - // For hitobjects which continue in the direction of the slider, the player will normally follow through the slider, - // such that they're not jumping from the lazy position but rather from very close to (or the end of) the slider. - // In such cases, a leniency is applied by also considering the jump distance from the tail of the slider, and taking the minimum jump distance. - // Additional distance is removed based on position of jump relative to slider follow circle radius. - // JumpDistance is the leniency distance beyond the assumed_slider_radius. tailJumpDistance is maximum_slider_radius since the full distance of radial leniency is still possible. + float tailJumpDistance = Vector2.Subtract(lastSlider.TailCircle.StackedPosition, BaseObject.StackedPosition).Length * scalingFactor; MovementDistance = Math.Max(0, Math.Min(MovementDistance - (maximum_slider_radius - assumed_slider_radius), tailJumpDistance - maximum_slider_radius)); } From a081038076f647b6fa7632135f71766b8a97514c Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 24 Nov 2021 13:01:53 +0900 Subject: [PATCH 004/146] Normalized -> Normalised --- .../Preprocessing/OsuDifficultyHitObject.cs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs index ecba2500d0..5c20851e23 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs @@ -11,10 +11,10 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing { public class OsuDifficultyHitObject : DifficultyHitObject { - private const int normalized_radius = 50; // Change radius to 50 to make 100 the diameter. Easier for mental maths. + private const int normalised_radius = 50; // Change radius to 50 to make 100 the diameter. Easier for mental maths. private const int min_delta_time = 25; - private const float maximum_slider_radius = normalized_radius * 2.4f; - private const float assumed_slider_radius = normalized_radius * 1.8f; + private const float maximum_slider_radius = normalised_radius * 2.4f; + private const float assumed_slider_radius = normalised_radius * 1.8f; protected new OsuHitObject BaseObject => (OsuHitObject)base.BaseObject; @@ -24,7 +24,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing public readonly double StrainTime; /// - /// Normalized distance from the end position of the previous to the start position of this . + /// Normalised distance from the end position of the previous to the start position of this . /// public double JumpDistance { get; private set; } @@ -39,7 +39,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing public double MovementTime { get; private set; } /// - /// Normalized distance between the start and end position of this . + /// Normalised distance between the start and end position of this . /// public double TravelDistance { get; private set; } @@ -76,7 +76,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing return; // We will scale distances by this factor, so we can assume a uniform CircleSize among beatmaps. - float scalingFactor = normalized_radius / (float)BaseObject.Radius; + float scalingFactor = normalised_radius / (float)BaseObject.Radius; if (BaseObject.Radius < 30) { @@ -153,7 +153,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing slider.LazyEndPosition = slider.StackedPosition + slider.Path.PositionAt(endTimeMin); // temporary lazy end position until a real result can be derived. var currCursorPosition = slider.StackedPosition; - double scalingFactor = normalized_radius / slider.Radius; // lazySliderDistance is coded to be sensitive to scaling, this makes the maths easier with the thresholds being used. + double scalingFactor = normalised_radius / slider.Radius; // lazySliderDistance is coded to be sensitive to scaling, this makes the maths easier with the thresholds being used. for (int i = 1; i < slider.NestedHitObjects.Count; i++) { @@ -181,7 +181,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing else if (currMovementObj is SliderRepeat) { // For a slider repeat, assume a tighter movement threshold to better assess repeat sliders. - requiredMovement = normalized_radius; + requiredMovement = normalised_radius; } if (currMovementLength > requiredMovement) From b5747f351dac23d33f63b3b804ea1052a7c5a5de Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 24 Nov 2021 13:11:44 +0900 Subject: [PATCH 005/146] Reword xmldocs --- .../Difficulty/Preprocessing/OsuDifficultyHitObject.cs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs index 5c20851e23..52108f85f9 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs @@ -29,12 +29,15 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing public double JumpDistance { get; private set; } /// - /// Minimum distance from the end position of the previous to the start position of this . + /// Normalised minimum distance from the end position of the previous to the start position of this . /// + /// + /// This is bounded by , but may be smaller if a more natural path is able to be taken through a preceding slider. + /// public double MovementDistance { get; private set; } /// - /// Milliseconds elapsed since the end time of the previous , with a minimum of 25ms. + /// The time taken to travel through , with a minimum value of 25ms. /// public double MovementTime { get; private set; } @@ -44,7 +47,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing public double TravelDistance { get; private set; } /// - /// Milliseconds elapsed between the start and end time of this , with a minimum of 25ms. + /// The time taken to travel through , with a minimum value of 25ms for a non-zero distance. /// public double TravelTime { get; private set; } From 274444ed6725345e3b4f0588fc046d1bba95a79f Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 24 Nov 2021 13:22:52 +0900 Subject: [PATCH 006/146] Add additional information to diagram --- .../Preprocessing/OsuDifficultyHitObject.cs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs index 52108f85f9..36a1053317 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs @@ -103,7 +103,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing if (lastObject is Slider lastSlider) { double lastTravelTime = Math.Max(lastSlider.LazyTravelTime / clockRate, min_delta_time); - MovementTime = Math.Max(MovementTime - lastTravelTime, min_delta_time); + MovementTime = Math.Max(StrainTime - lastTravelTime, min_delta_time); // // We'll try to better approximate the real movements a player will take in patterns following on from sliders. Consider the following slider-to-object patterns: @@ -117,14 +117,17 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing // // Where "<==>" represents a slider, and "o" represents where the cursor needs to be for either hitobject (for a slider, this is the lazy cursor position). // - // Case (1) is an anti-flow pattern, where players will cut the slider short in order to move to the next object. The jump pattern is (o--o). - // Case (2) is a flow pattern, where players will follow the slider through to its visual extent. The jump pattern is (>--o). + // The pattern (o--o) has distance JumpDistance. + // The pattern (>--o) is a new distance we'll call "tailJumpDistance". + // + // Case (1) is an anti-flow pattern, where players will cut the slider short in order to move to the next object. The most natural jump pattern is (o--o). + // Case (2) is a flow pattern, where players will follow the slider through to its visual extent. The most natural jump pattern is (>--o). // // A lenience is applied by assuming that the player jumps the minimum of these two distances in all cases. // float tailJumpDistance = Vector2.Subtract(lastSlider.TailCircle.StackedPosition, BaseObject.StackedPosition).Length * scalingFactor; - MovementDistance = Math.Max(0, Math.Min(MovementDistance - (maximum_slider_radius - assumed_slider_radius), tailJumpDistance - maximum_slider_radius)); + MovementDistance = Math.Max(0, Math.Min(JumpDistance - (maximum_slider_radius - assumed_slider_radius), tailJumpDistance - maximum_slider_radius)); } if (lastLastObject != null && !(lastLastObject is Spinner)) From b20ff22af0c41294dfffedb6717e6fc78c43ec3b Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 24 Nov 2021 16:50:33 +0900 Subject: [PATCH 007/146] Ensure travel distance is calculated for all sliders --- .../Preprocessing/OsuDifficultyHitObject.cs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs index 36a1053317..f1f246359a 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs @@ -74,6 +74,13 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing private void setDistances(double clockRate) { + if (BaseObject is Slider currentSlider) + { + computeSliderCursorPosition(currentSlider); + TravelDistance = currentSlider.LazyTravelDistance; + TravelTime = Math.Max(currentSlider.LazyTravelTime / clockRate, min_delta_time); + } + // We don't need to calculate either angle or distance when one of the last->curr objects is a spinner if (BaseObject is Spinner || lastObject is Spinner) return; @@ -87,13 +94,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing scalingFactor *= 1 + smallCircleBonus; } - if (BaseObject is Slider currentSlider) - { - computeSliderCursorPosition(currentSlider); - TravelDistance = currentSlider.LazyTravelDistance; - TravelTime = Math.Max(currentSlider.LazyTravelTime / clockRate, min_delta_time); - } - Vector2 lastCursorPosition = getEndCursorPosition(lastObject); JumpDistance = (BaseObject.StackedPosition * scalingFactor - lastCursorPosition * scalingFactor).Length; From 3e4b774992b7aa811d60bc75e5b7979ba4889f07 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 25 Nov 2021 14:08:08 +0900 Subject: [PATCH 008/146] Invert lines for better chronological order --- osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs index 4ddcbb7770..d2a1083f29 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs @@ -49,8 +49,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills // But if the last object is a slider, then we extend the travel velocity through the slider into the current object. if (osuLastObj.BaseObject is Slider && withSliders) { - double movementVelocity = osuCurrObj.MovementDistance / osuCurrObj.MovementTime; // calculate the movement velocity from slider end to current object double travelVelocity = osuLastObj.TravelDistance / osuLastObj.TravelTime; // calculate the slider velocity from slider head to slider end. + double movementVelocity = osuCurrObj.MovementDistance / osuCurrObj.MovementTime; // calculate the movement velocity from slider end to current object currVelocity = Math.Max(currVelocity, movementVelocity + travelVelocity); // take the larger total combined velocity. } @@ -60,8 +60,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills if (osuLastLastObj.BaseObject is Slider && withSliders) { - double movementVelocity = osuLastObj.MovementDistance / osuLastObj.MovementTime; double travelVelocity = osuLastLastObj.TravelDistance / osuLastLastObj.TravelTime; + double movementVelocity = osuLastObj.MovementDistance / osuLastObj.MovementTime; prevVelocity = Math.Max(prevVelocity, movementVelocity + travelVelocity); } From 0a6c221de4dda5d3f41c504974f204b79be4f0ab Mon Sep 17 00:00:00 2001 From: tbrose Date: Mon, 6 Dec 2021 22:07:47 +0100 Subject: [PATCH 009/146] Adds tests for checkContainsUsername function of MessageNotifier component --- .../Online/Chat/MessageNotifierTest.cs | 66 +++++++++++++++++++ osu.Game/Online/Chat/MessageNotifier.cs | 2 +- 2 files changed, 67 insertions(+), 1 deletion(-) create mode 100644 osu.Game.Tests/Online/Chat/MessageNotifierTest.cs diff --git a/osu.Game.Tests/Online/Chat/MessageNotifierTest.cs b/osu.Game.Tests/Online/Chat/MessageNotifierTest.cs new file mode 100644 index 0000000000..9fed72d249 --- /dev/null +++ b/osu.Game.Tests/Online/Chat/MessageNotifierTest.cs @@ -0,0 +1,66 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using NUnit.Framework; +using osu.Game.Online.Chat; + +namespace osu.Game.Tests.Online.Chat +{ + [TestFixture] + public class TestCheckUsername + { + [Test] + public void TestContainsUsernameMidlinePositive() + { + Assert.IsTrue(MessageNotifier.checkContainsUsername("This is a test message", "Test")); + } + + [Test] + public void TestContainsUsernameStartOfLinePositive() + { + Assert.IsTrue(MessageNotifier.checkContainsUsername("Test message", "Test")); + } + + [Test] + public void TestContainsUsernameEndOfLinePositive() + { + Assert.IsTrue(MessageNotifier.checkContainsUsername("This is a test", "Test")); + } + + [Test] + public void TestContainsUsernameMidlineNegative() + { + Assert.IsFalse(MessageNotifier.checkContainsUsername("This is a testmessage for notifications", "Test")); + } + + [Test] + public void TestContainsUsernameStartOfLineNegative() + { + Assert.IsFalse(MessageNotifier.checkContainsUsername("Testmessage", "Test")); + } + + [Test] + public void TestContainsUsernameEndOfLineNegative() + { + Assert.IsFalse(MessageNotifier.checkContainsUsername("This is a notificationtest", "Test")); + } + + [Test] + public void TestContainsUsernameBetweenInterpunction() + { + Assert.IsTrue(MessageNotifier.checkContainsUsername("Hello 'test'-message", "Test")); + } + + [Test] + public void TestContainsUsernameUnicode() + { + Assert.IsTrue(MessageNotifier.checkContainsUsername("Test \u0460\u0460 message", "\u0460\u0460")); + } + + [Test] + public void TestContainsUsernameUnicodeNegative() + { + Assert.IsFalse(MessageNotifier.checkContainsUsername("Test ha\u0460\u0460o message", "\u0460\u0460")); + } + } +} diff --git a/osu.Game/Online/Chat/MessageNotifier.cs b/osu.Game/Online/Chat/MessageNotifier.cs index ca6317566f..69b3b18b3e 100644 --- a/osu.Game/Online/Chat/MessageNotifier.cs +++ b/osu.Game/Online/Chat/MessageNotifier.cs @@ -129,7 +129,7 @@ namespace osu.Game.Online.Chat /// Checks if contains . /// This will match against the case where underscores are used instead of spaces (which is how osu-stable handles usernames with spaces). /// - private static bool checkContainsUsername(string message, string username) => message.Contains(username, StringComparison.OrdinalIgnoreCase) || message.Contains(username.Replace(' ', '_'), StringComparison.OrdinalIgnoreCase); + public static bool checkContainsUsername(string message, string username) => message.Contains(username, StringComparison.OrdinalIgnoreCase) || message.Contains(username.Replace(' ', '_'), StringComparison.OrdinalIgnoreCase); public class PrivateMessageNotification : OpenChannelNotification { From 39594b7362747b329bb9486ab7b53019d15671fe Mon Sep 17 00:00:00 2001 From: tbrose Date: Mon, 6 Dec 2021 23:32:21 +0100 Subject: [PATCH 010/146] Fixes detection of mentioning of user falsely detects messages where the username is coincidentally contained in words of a message. --- osu.Game/Online/Chat/MessageNotifier.cs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/osu.Game/Online/Chat/MessageNotifier.cs b/osu.Game/Online/Chat/MessageNotifier.cs index 69b3b18b3e..aca9df0600 100644 --- a/osu.Game/Online/Chat/MessageNotifier.cs +++ b/osu.Game/Online/Chat/MessageNotifier.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.Collections.Specialized; using System.Linq; +using System.Text.RegularExpressions; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -126,10 +127,14 @@ namespace osu.Game.Online.Chat } /// - /// Checks if contains . + /// Checks if mentions . /// This will match against the case where underscores are used instead of spaces (which is how osu-stable handles usernames with spaces). /// - public static bool checkContainsUsername(string message, string username) => message.Contains(username, StringComparison.OrdinalIgnoreCase) || message.Contains(username.Replace(' ', '_'), StringComparison.OrdinalIgnoreCase); + public static bool checkContainsUsername(string message, string username) { + string fullName = Regex.Escape(username); + string underscoreName = Regex.Escape(username.Replace(' ', '_')); + return new Regex($"\\b({fullName}|{underscoreName})\\b", RegexOptions.IgnoreCase).Matches(message).Count > 0; + } public class PrivateMessageNotification : OpenChannelNotification { From f02e44d552495823285f23f5e9af8138b70ec2eb Mon Sep 17 00:00:00 2001 From: tbrose Date: Tue, 7 Dec 2021 01:38:37 +0100 Subject: [PATCH 011/146] Fixes not matching coding style --- osu.Game.Tests/Online/Chat/MessageNotifierTest.cs | 2 +- osu.Game/Online/Chat/MessageNotifier.cs | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Online/Chat/MessageNotifierTest.cs b/osu.Game.Tests/Online/Chat/MessageNotifierTest.cs index 9fed72d249..81baaef2d2 100644 --- a/osu.Game.Tests/Online/Chat/MessageNotifierTest.cs +++ b/osu.Game.Tests/Online/Chat/MessageNotifierTest.cs @@ -7,7 +7,7 @@ using osu.Game.Online.Chat; namespace osu.Game.Tests.Online.Chat { [TestFixture] - public class TestCheckUsername + public class MessageNotifierTest { [Test] public void TestContainsUsernameMidlinePositive() diff --git a/osu.Game/Online/Chat/MessageNotifier.cs b/osu.Game/Online/Chat/MessageNotifier.cs index aca9df0600..fbbf8fff38 100644 --- a/osu.Game/Online/Chat/MessageNotifier.cs +++ b/osu.Game/Online/Chat/MessageNotifier.cs @@ -130,7 +130,8 @@ namespace osu.Game.Online.Chat /// Checks if mentions . /// This will match against the case where underscores are used instead of spaces (which is how osu-stable handles usernames with spaces). /// - public static bool checkContainsUsername(string message, string username) { + public static bool checkContainsUsername(string message, string username) + { string fullName = Regex.Escape(username); string underscoreName = Regex.Escape(username.Replace(' ', '_')); return new Regex($"\\b({fullName}|{underscoreName})\\b", RegexOptions.IgnoreCase).Matches(message).Count > 0; From 7a0d4fca17fd22f72a721bb18f20ab387dbb260d Mon Sep 17 00:00:00 2001 From: tbrose Date: Tue, 7 Dec 2021 01:41:21 +0100 Subject: [PATCH 012/146] Fixes using Matches+Count instead of IsMatch negatively affecting performance --- osu.Game/Online/Chat/MessageNotifier.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Online/Chat/MessageNotifier.cs b/osu.Game/Online/Chat/MessageNotifier.cs index fbbf8fff38..4ae4e64855 100644 --- a/osu.Game/Online/Chat/MessageNotifier.cs +++ b/osu.Game/Online/Chat/MessageNotifier.cs @@ -134,7 +134,7 @@ namespace osu.Game.Online.Chat { string fullName = Regex.Escape(username); string underscoreName = Regex.Escape(username.Replace(' ', '_')); - return new Regex($"\\b({fullName}|{underscoreName})\\b", RegexOptions.IgnoreCase).Matches(message).Count > 0; + return new Regex($"\\b({fullName}|{underscoreName})\\b", RegexOptions.IgnoreCase).IsMatch(message); } public class PrivateMessageNotification : OpenChannelNotification From 0a8c4f4cecfa4e483f97decf6a6383102aadf228 Mon Sep 17 00:00:00 2001 From: tbrose Date: Tue, 7 Dec 2021 01:55:45 +0100 Subject: [PATCH 013/146] Adds test cases for usernames with special characters --- osu.Game.Tests/Online/Chat/MessageNotifierTest.cs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/osu.Game.Tests/Online/Chat/MessageNotifierTest.cs b/osu.Game.Tests/Online/Chat/MessageNotifierTest.cs index 81baaef2d2..25dbdb9ea2 100644 --- a/osu.Game.Tests/Online/Chat/MessageNotifierTest.cs +++ b/osu.Game.Tests/Online/Chat/MessageNotifierTest.cs @@ -62,5 +62,17 @@ namespace osu.Game.Tests.Online.Chat { Assert.IsFalse(MessageNotifier.checkContainsUsername("Test ha\u0460\u0460o message", "\u0460\u0460")); } + + [Test] + public void TestContainsUsernameSpecialCharactersPositive() + { + Assert.IsTrue(MessageNotifier.checkContainsUsername("Test [#^-^#] message", "[#^-^#]")); + } + + [Test] + public void TestContainsUsernameSpecialCharactersNegative() + { + Assert.IsFalse(MessageNotifier.checkContainsUsername("Test pad[#^-^#]oru message", "[#^-^#]")); + } } } From b6d47a41f411959a4fe2105f7fa25eff76f46dc2 Mon Sep 17 00:00:00 2001 From: tbrose Date: Tue, 7 Dec 2021 02:14:40 +0100 Subject: [PATCH 014/146] Adjusted RegEx pattern to also take special characters into account --- osu.Game/Online/Chat/MessageNotifier.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Online/Chat/MessageNotifier.cs b/osu.Game/Online/Chat/MessageNotifier.cs index 4ae4e64855..883f6a7284 100644 --- a/osu.Game/Online/Chat/MessageNotifier.cs +++ b/osu.Game/Online/Chat/MessageNotifier.cs @@ -134,7 +134,7 @@ namespace osu.Game.Online.Chat { string fullName = Regex.Escape(username); string underscoreName = Regex.Escape(username.Replace(' ', '_')); - return new Regex($"\\b({fullName}|{underscoreName})\\b", RegexOptions.IgnoreCase).IsMatch(message); + return new Regex($"(^|^\\W|\\W|\\w\\W)({fullName}|{underscoreName})($|$\\W|\\W|\\w\\W)", RegexOptions.IgnoreCase).IsMatch(message); } public class PrivateMessageNotification : OpenChannelNotification From be86ca582cc114788c28e44fcb487e99e1c32dbf Mon Sep 17 00:00:00 2001 From: tbrose Date: Tue, 7 Dec 2021 02:34:35 +0100 Subject: [PATCH 015/146] Adds test cases for at-sign and colon adjacent to the username --- osu.Game.Tests/Online/Chat/MessageNotifierTest.cs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/osu.Game.Tests/Online/Chat/MessageNotifierTest.cs b/osu.Game.Tests/Online/Chat/MessageNotifierTest.cs index 25dbdb9ea2..b885299d1f 100644 --- a/osu.Game.Tests/Online/Chat/MessageNotifierTest.cs +++ b/osu.Game.Tests/Online/Chat/MessageNotifierTest.cs @@ -74,5 +74,17 @@ namespace osu.Game.Tests.Online.Chat { Assert.IsFalse(MessageNotifier.checkContainsUsername("Test pad[#^-^#]oru message", "[#^-^#]")); } + + [Test] + public void TestContainsUsernameAtSign() + { + Assert.IsTrue(MessageNotifier.checkContainsUsername("@username hi", "username")); + } + + [Test] + public void TestContainsUsernameColon() + { + Assert.IsTrue(MessageNotifier.checkContainsUsername("username: hi", "username")); + } } } From 882223b27f684cbc9c02b4365f4f3728862a808a Mon Sep 17 00:00:00 2001 From: tbrose Date: Tue, 7 Dec 2021 02:38:10 +0100 Subject: [PATCH 016/146] Using static call and verbatim symbol and optimizes regex pattern for username check --- osu.Game/Online/Chat/MessageNotifier.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Online/Chat/MessageNotifier.cs b/osu.Game/Online/Chat/MessageNotifier.cs index 883f6a7284..db7c5e47f5 100644 --- a/osu.Game/Online/Chat/MessageNotifier.cs +++ b/osu.Game/Online/Chat/MessageNotifier.cs @@ -134,7 +134,7 @@ namespace osu.Game.Online.Chat { string fullName = Regex.Escape(username); string underscoreName = Regex.Escape(username.Replace(' ', '_')); - return new Regex($"(^|^\\W|\\W|\\w\\W)({fullName}|{underscoreName})($|$\\W|\\W|\\w\\W)", RegexOptions.IgnoreCase).IsMatch(message); + return Regex.IsMatch(message, $@"(^|\W)({fullName}|{underscoreName})($|\W)", RegexOptions.IgnoreCase); } public class PrivateMessageNotification : OpenChannelNotification From ded86282c1b44574b8f1fe1711766640b77b17b4 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 7 Dec 2021 23:14:35 +0900 Subject: [PATCH 017/146] Rename + better documentation --- .../Preprocessing/OsuDifficultyHitObject.cs | 37 +++++++++++++------ .../Difficulty/Skills/Aim.cs | 16 ++++---- .../Difficulty/Skills/Flashlight.cs | 2 +- .../Difficulty/Skills/Speed.cs | 2 +- 4 files changed, 35 insertions(+), 22 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs index f1f246359a..8ecba7375c 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs @@ -24,22 +24,35 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing public readonly double StrainTime; /// - /// Normalised distance from the end position of the previous to the start position of this . + /// Normalised distance from the "lazy" end position of the previous to the start position of this . + /// + /// The "lazy" end position is the position at which the cursor ends up if the previous hitobject is followed with as minimal movement as possible (i.e. on the edge of slider follow circles). + /// /// - public double JumpDistance { get; private set; } + public double LazyJumpDistance { get; private set; } /// - /// Normalised minimum distance from the end position of the previous to the start position of this . + /// Normalised shortest distance to consider for a jump between the previous and this . /// /// - /// This is bounded by , but may be smaller if a more natural path is able to be taken through a preceding slider. + /// This is bounded from above by , and is smaller than the former if a more natural path is able to be taken through the previous . /// - public double MovementDistance { get; private set; } + /// + /// Suppose a linear slider - circle pattern. + ///
+ /// Following the slider lazily (see: ) will result in underestimating the true end position of the slider as being closer towards the start position. + /// As a result, overestimates the jump distance because the player is able to take a more natural path by following through the slider to its end, + /// such that the jump is felt as only starting from the slider's true end position. + ///
+ /// Now consider a slider - circle pattern where the circle is stacked along the path inside the slider. + /// In this case, the lazy end position correctly estimates the true end position of the slider and provides the more natural movement path. + ///
+ public double MinimumJumpDistance { get; private set; } /// - /// The time taken to travel through , with a minimum value of 25ms. + /// The time taken to travel through , with a minimum value of 25ms. /// - public double MovementTime { get; private set; } + public double MinimumJumpTime { get; private set; } /// /// Normalised distance between the start and end position of this . @@ -96,14 +109,14 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing Vector2 lastCursorPosition = getEndCursorPosition(lastObject); - JumpDistance = (BaseObject.StackedPosition * scalingFactor - lastCursorPosition * scalingFactor).Length; - MovementTime = StrainTime; - MovementDistance = JumpDistance; + LazyJumpDistance = (BaseObject.StackedPosition * scalingFactor - lastCursorPosition * scalingFactor).Length; + MinimumJumpTime = StrainTime; + MinimumJumpDistance = LazyJumpDistance; if (lastObject is Slider lastSlider) { double lastTravelTime = Math.Max(lastSlider.LazyTravelTime / clockRate, min_delta_time); - MovementTime = Math.Max(StrainTime - lastTravelTime, min_delta_time); + MinimumJumpTime = Math.Max(StrainTime - lastTravelTime, min_delta_time); // // We'll try to better approximate the real movements a player will take in patterns following on from sliders. Consider the following slider-to-object patterns: @@ -127,7 +140,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing // float tailJumpDistance = Vector2.Subtract(lastSlider.TailCircle.StackedPosition, BaseObject.StackedPosition).Length * scalingFactor; - MovementDistance = Math.Max(0, Math.Min(JumpDistance - (maximum_slider_radius - assumed_slider_radius), tailJumpDistance - maximum_slider_radius)); + MinimumJumpDistance = Math.Max(0, Math.Min(LazyJumpDistance - (maximum_slider_radius - assumed_slider_radius), tailJumpDistance - maximum_slider_radius)); } if (lastLastObject != null && !(lastLastObject is Spinner)) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs index d2a1083f29..a6301aed6d 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs @@ -44,24 +44,24 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills var osuLastLastObj = (OsuDifficultyHitObject)Previous[1]; // Calculate the velocity to the current hitobject, which starts with a base distance / time assuming the last object is a hitcircle. - double currVelocity = osuCurrObj.JumpDistance / osuCurrObj.StrainTime; + double currVelocity = osuCurrObj.LazyJumpDistance / osuCurrObj.StrainTime; // But if the last object is a slider, then we extend the travel velocity through the slider into the current object. if (osuLastObj.BaseObject is Slider && withSliders) { double travelVelocity = osuLastObj.TravelDistance / osuLastObj.TravelTime; // calculate the slider velocity from slider head to slider end. - double movementVelocity = osuCurrObj.MovementDistance / osuCurrObj.MovementTime; // calculate the movement velocity from slider end to current object + double movementVelocity = osuCurrObj.MinimumJumpDistance / osuCurrObj.MinimumJumpTime; // calculate the movement velocity from slider end to current object currVelocity = Math.Max(currVelocity, movementVelocity + travelVelocity); // take the larger total combined velocity. } // As above, do the same for the previous hitobject. - double prevVelocity = osuLastObj.JumpDistance / osuLastObj.StrainTime; + double prevVelocity = osuLastObj.LazyJumpDistance / osuLastObj.StrainTime; if (osuLastLastObj.BaseObject is Slider && withSliders) { double travelVelocity = osuLastLastObj.TravelDistance / osuLastLastObj.TravelTime; - double movementVelocity = osuLastObj.MovementDistance / osuLastObj.MovementTime; + double movementVelocity = osuLastObj.MinimumJumpDistance / osuLastObj.MinimumJumpTime; prevVelocity = Math.Max(prevVelocity, movementVelocity + travelVelocity); } @@ -94,7 +94,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills acuteAngleBonus *= calcAcuteAngleBonus(lastAngle) // Multiply by previous angle, we don't want to buff unless this is a wiggle type pattern. * Math.Min(angleBonus, 125 / osuCurrObj.StrainTime) // The maximum velocity we buff is equal to 125 / strainTime * Math.Pow(Math.Sin(Math.PI / 2 * Math.Min(1, (100 - osuCurrObj.StrainTime) / 25)), 2) // scale buff from 150 bpm 1/4 to 200 bpm 1/4 - * Math.Pow(Math.Sin(Math.PI / 2 * (Math.Clamp(osuCurrObj.JumpDistance, 50, 100) - 50) / 50), 2); // Buff distance exceeding 50 (radius) up to 100 (diameter). + * Math.Pow(Math.Sin(Math.PI / 2 * (Math.Clamp(osuCurrObj.LazyJumpDistance, 50, 100) - 50) / 50), 2); // Buff distance exceeding 50 (radius) up to 100 (diameter). } // Penalize wide angles if they're repeated, reducing the penalty as the lastAngle gets more acute. @@ -107,8 +107,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills if (Math.Max(prevVelocity, currVelocity) != 0) { // We want to use the average velocity over the whole object when awarding differences, not the individual jump and slider path velocities. - prevVelocity = (osuLastObj.JumpDistance + osuLastLastObj.TravelDistance) / osuLastObj.StrainTime; - currVelocity = (osuCurrObj.JumpDistance + osuLastObj.TravelDistance) / osuCurrObj.StrainTime; + prevVelocity = (osuLastObj.LazyJumpDistance + osuLastLastObj.TravelDistance) / osuLastObj.StrainTime; + currVelocity = (osuCurrObj.LazyJumpDistance + osuLastObj.TravelDistance) / osuCurrObj.StrainTime; // Scale with ratio of difference compared to 0.5 * max dist. double distRatio = Math.Pow(Math.Sin(Math.PI / 2 * Math.Abs(prevVelocity - currVelocity) / Math.Max(prevVelocity, currVelocity)), 2); @@ -119,7 +119,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills // Reward for % distance slowed down compared to previous, paying attention to not award overlap double nonOverlapVelocityBuff = Math.Abs(prevVelocity - currVelocity) // do not award overlap - * Math.Pow(Math.Sin(Math.PI / 2 * Math.Min(1, Math.Min(osuCurrObj.JumpDistance, osuLastObj.JumpDistance) / 100)), 2); + * Math.Pow(Math.Sin(Math.PI / 2 * Math.Min(1, Math.Min(osuCurrObj.LazyJumpDistance, osuLastObj.LazyJumpDistance) / 100)), 2); // Choose the largest bonus, multiplied by ratio. velocityChangeBonus = Math.Max(overlapVelocityBuff, nonOverlapVelocityBuff) * distRatio; diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs index 466f0556ab..44ba0e2057 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs @@ -56,7 +56,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills smallDistNerf = Math.Min(1.0, jumpDistance / 75.0); // We also want to nerf stacks so that only the first object of the stack is accounted for. - double stackNerf = Math.Min(1.0, (osuPrevious.JumpDistance / scalingFactor) / 25.0); + double stackNerf = Math.Min(1.0, (osuPrevious.LazyJumpDistance / scalingFactor) / 25.0); result += Math.Pow(0.8, i) * stackNerf * scalingFactor * jumpDistance / cumulativeStrainTime; } diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs index b53d287ee6..75a9b13bdf 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs @@ -155,7 +155,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills speedBonus = 1 + 0.75 * Math.Pow((min_speed_bonus - strainTime) / speed_balancing_factor, 2); double travelDistance = osuPrevObj?.TravelDistance ?? 0; - double distance = Math.Min(single_spacing_threshold, travelDistance + osuCurrObj.JumpDistance); + double distance = Math.Min(single_spacing_threshold, travelDistance + osuCurrObj.LazyJumpDistance); return (speedBonus + speedBonus * Math.Pow(distance / single_spacing_threshold, 3.5)) / strainTime; } From 0b0ff361542521ea066c06d2bad6955c2905873a Mon Sep 17 00:00:00 2001 From: Susko3 <16479013+Susko3@users.noreply.github.com> Date: Tue, 7 Dec 2021 20:06:22 +0100 Subject: [PATCH 018/146] Allow only number characters parseable by `int.TryParse` char.IsNumber() is too broad, allowing full width and other numbers. --- osu.Game/Overlays/Settings/SettingsNumberBox.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Settings/SettingsNumberBox.cs b/osu.Game/Overlays/Settings/SettingsNumberBox.cs index cbe9f7fc64..0a4949f8b6 100644 --- a/osu.Game/Overlays/Settings/SettingsNumberBox.cs +++ b/osu.Game/Overlays/Settings/SettingsNumberBox.cs @@ -67,7 +67,7 @@ namespace osu.Game.Overlays.Settings private class OutlinedNumberBox : OutlinedTextBox { - protected override bool CanAddCharacter(char character) => char.IsNumber(character); + protected override bool CanAddCharacter(char character) => character >= '0' && character <= '9'; public new void NotifyInputError() => base.NotifyInputError(); } From a969fe3ef811a64c0e05a70342645782dd9d4f77 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 8 Dec 2021 13:37:46 +0900 Subject: [PATCH 019/146] Add test coverage showing intended UX of user's volume levels being retained when unmuting --- osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs index 06eaa726c9..958d617d63 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs @@ -251,7 +251,12 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestMutedNotificationMuteButton() { - addVolumeSteps("mute button", () => volumeOverlay.IsMuted.Value = true, () => !volumeOverlay.IsMuted.Value); + addVolumeSteps("mute button", () => + { + // Importantly, in the case the volume is muted but the user has a volume level set, it should be retained. + audioManager.VolumeTrack.Value = 0.5f; + volumeOverlay.IsMuted.Value = true; + }, () => !volumeOverlay.IsMuted.Value && audioManager.VolumeTrack.Value == 0.5f); } /// From 0775053a18a711c8eb9fff57a26ff80c8b98caca Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 8 Dec 2021 13:38:13 +0900 Subject: [PATCH 020/146] Fix the unmute notification potentially overwriting user's volume levels unnecessarily I've also changed the cutoffs to 5% rather than zero, as this seems like a saner method of showing this dialog. With levels 5% or less, the game is basically inaudible. Arguably, the cutoff can be increased to 10%. --- osu.Game/Screens/Play/PlayerLoader.cs | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs index 57db411571..dfc3c2b61d 100644 --- a/osu.Game/Screens/Play/PlayerLoader.cs +++ b/osu.Game/Screens/Play/PlayerLoader.cs @@ -468,12 +468,14 @@ namespace osu.Game.Screens.Play private int restartCount; + private const double volume_requirement = 0.05; + private void showMuteWarningIfNeeded() { if (!muteWarningShownOnce.Value) { // Checks if the notification has not been shown yet and also if master volume is muted, track/music volume is muted or if the whole game is muted. - if (volumeOverlay?.IsMuted.Value == true || audioManager.Volume.Value <= audioManager.Volume.MinValue || audioManager.VolumeTrack.Value <= audioManager.VolumeTrack.MinValue) + if (volumeOverlay?.IsMuted.Value == true || audioManager.Volume.Value <= volume_requirement || audioManager.VolumeTrack.Value <= volume_requirement) { notificationOverlay?.Post(new MutedNotification()); muteWarningShownOnce.Value = true; @@ -487,7 +489,7 @@ namespace osu.Game.Screens.Play public MutedNotification() { - Text = "Your music volume is set to 0%! Click here to restore it."; + Text = "Your game volume is too low to hear anything! Click here to restore it."; } [BackgroundDependencyLoader] @@ -501,8 +503,12 @@ namespace osu.Game.Screens.Play notificationOverlay.Hide(); volumeOverlay.IsMuted.Value = false; - audioManager.Volume.SetDefault(); - audioManager.VolumeTrack.SetDefault(); + + // Check values before resetting, as the user may have only had mute enabled, in which case we might not need to adjust volumes. + if (audioManager.Volume.Value < volume_requirement) + audioManager.Volume.SetDefault(); + if (audioManager.VolumeTrack.Value < volume_requirement) + audioManager.VolumeTrack.SetDefault(); return true; }; From 7c0f7b1baac4fbc16791f2208605f7a77ec78920 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 8 Dec 2021 14:57:21 +0900 Subject: [PATCH 021/146] Use "x" for cursor position in diagrams --- .../Preprocessing/OsuDifficultyHitObject.cs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs index 8ecba7375c..1b6914bfaf 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs @@ -121,20 +121,20 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing // // We'll try to better approximate the real movements a player will take in patterns following on from sliders. Consider the following slider-to-object patterns: // - // 1. <======o==> + // 1. <======x==> // | / - // o + // x // - // 2. <======o==>---o + // 2. <======x==>---x // |______| // - // Where "<==>" represents a slider, and "o" represents where the cursor needs to be for either hitobject (for a slider, this is the lazy cursor position). + // Where "<==>" represents a slider, and "x" represents where the cursor needs to be for either hitobject (for a slider, this is the lazy cursor position). // - // The pattern (o--o) has distance JumpDistance. - // The pattern (>--o) is a new distance we'll call "tailJumpDistance". + // The pattern (x--x) has distance JumpDistance. + // The pattern (>--x) is a new distance we'll call "tailJumpDistance". // - // Case (1) is an anti-flow pattern, where players will cut the slider short in order to move to the next object. The most natural jump pattern is (o--o). - // Case (2) is a flow pattern, where players will follow the slider through to its visual extent. The most natural jump pattern is (>--o). + // Case (1) is an anti-flow pattern, where players will cut the slider short in order to move to the next object. The most natural jump pattern is (x--x). + // Case (2) is a flow pattern, where players will follow the slider through to its visual extent. The most natural jump pattern is (>--x). // // A lenience is applied by assuming that the player jumps the minimum of these two distances in all cases. // From 814f072767f52ceb576eb8d7ecfdeceb14340ae7 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 8 Dec 2021 15:17:56 +0900 Subject: [PATCH 022/146] Use new LazyJumpDistance terminology in documentation --- .../Difficulty/Preprocessing/OsuDifficultyHitObject.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs index 1b6914bfaf..6c354cfa99 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs @@ -130,7 +130,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing // // Where "<==>" represents a slider, and "x" represents where the cursor needs to be for either hitobject (for a slider, this is the lazy cursor position). // - // The pattern (x--x) has distance JumpDistance. + // The pattern (x--x) has distance LazyJumpDistance. // The pattern (>--x) is a new distance we'll call "tailJumpDistance". // // Case (1) is an anti-flow pattern, where players will cut the slider short in order to move to the next object. The most natural jump pattern is (x--x). From 11104124f1f923fa4c1f6bf06ac2441a7ac3cc0c Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 8 Dec 2021 15:52:59 +0900 Subject: [PATCH 023/146] Restructure doc for easier readability --- .../Preprocessing/OsuDifficultyHitObject.cs | 25 ++++++++++--------- 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs index 6c354cfa99..2c81b42e6c 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs @@ -119,24 +119,25 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing MinimumJumpTime = Math.Max(StrainTime - lastTravelTime, min_delta_time); // - // We'll try to better approximate the real movements a player will take in patterns following on from sliders. Consider the following slider-to-object patterns: + // There are two types of slider-to-object patterns to consider in order to better approximate the real movements a player will take. // - // 1. <======x==> - // | / - // x + // 1. The anti-flow pattern, where players cut the slider short in order to move to the next hitobject. // - // 2. <======x==>---x - // |______| + // <======o==> ← slider + // | ← most natural jump path + // o ← a follow-up hitcircle // - // Where "<==>" represents a slider, and "x" represents where the cursor needs to be for either hitobject (for a slider, this is the lazy cursor position). + // In this case the most natural jump path (o--o) is approximated by LazyJumpDistance. // - // The pattern (x--x) has distance LazyJumpDistance. - // The pattern (>--x) is a new distance we'll call "tailJumpDistance". + // 2. The flow pattern, where players follow through the slider to its visual extent into the next hitobject. // - // Case (1) is an anti-flow pattern, where players will cut the slider short in order to move to the next object. The most natural jump pattern is (x--x). - // Case (2) is a flow pattern, where players will follow the slider through to its visual extent. The most natural jump pattern is (>--x). + // <======o==>---o + // ↑ + // most natural jump path // - // A lenience is applied by assuming that the player jumps the minimum of these two distances in all cases. + // In this case the most natural movement path is better approximated by a new distance called "tailJumpDistance" - the distance between the slider's tail and the next hitobject. + // + // Thus, the player is assumed to jump the minimum of these two distances in all cases. // float tailJumpDistance = Vector2.Subtract(lastSlider.TailCircle.StackedPosition, BaseObject.StackedPosition).Length * scalingFactor; From 99991a6703887ffa1b95b4217005bf81db15be4d Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 8 Dec 2021 15:59:15 +0900 Subject: [PATCH 024/146] Minor cleanups, unifying wording a bit more --- .../Difficulty/Preprocessing/OsuDifficultyHitObject.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs index 2c81b42e6c..4df8ff0b12 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs @@ -119,7 +119,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing MinimumJumpTime = Math.Max(StrainTime - lastTravelTime, min_delta_time); // - // There are two types of slider-to-object patterns to consider in order to better approximate the real movements a player will take. + // There are two types of slider-to-object patterns to consider in order to better approximate the real movement a player will take to jump between the hitobjects. // // 1. The anti-flow pattern, where players cut the slider short in order to move to the next hitobject. // @@ -127,7 +127,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing // | ← most natural jump path // o ← a follow-up hitcircle // - // In this case the most natural jump path (o--o) is approximated by LazyJumpDistance. + // In this case the most natural jump path is approximated by LazyJumpDistance. // // 2. The flow pattern, where players follow through the slider to its visual extent into the next hitobject. // @@ -135,7 +135,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing // ↑ // most natural jump path // - // In this case the most natural movement path is better approximated by a new distance called "tailJumpDistance" - the distance between the slider's tail and the next hitobject. + // In this case the most natural jump path is better approximated by a new distance called "tailJumpDistance" - the distance between the slider's tail and the next hitobject. // // Thus, the player is assumed to jump the minimum of these two distances in all cases. // From beb5d61a42cc69841cfdbb1c8de0cc36d5973535 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 8 Dec 2021 20:38:18 +0900 Subject: [PATCH 025/146] Separate playlist item deletion to Playlists-specific class --- .../TestSceneDrawableRoomPlaylist.cs | 97 +-------- .../TestScenePlaylistsRoomPlaylist.cs | 188 ++++++++++++++++++ .../Components/MatchBeatmapDetailArea.cs | 3 +- .../OnlinePlay/DrawableRoomPlaylist.cs | 40 +--- .../DrawableRoomPlaylistWithResults.cs | 2 +- .../Playlists/PlaylistsRoomPlaylist.cs | 24 +++ .../Playlists/PlaylistsRoomSettingsOverlay.cs | 5 +- 7 files changed, 227 insertions(+), 132 deletions(-) create mode 100644 osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsRoomPlaylist.cs create mode 100644 osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomPlaylist.cs diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs index 55aa665ff1..c60b55d7e8 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs @@ -128,95 +128,6 @@ namespace osu.Game.Tests.Visual.Multiplayer AddAssert("item 1 is selected", () => playlist.SelectedItem.Value == playlist.Items[1]); } - [Test] - public void TestItemRemovedOnDeletion() - { - PlaylistItem selectedItem = null; - - createPlaylist(true, true); - - moveToItem(0); - AddStep("click", () => InputManager.Click(MouseButton.Left)); - AddStep("retrieve selection", () => selectedItem = playlist.SelectedItem.Value); - - moveToDeleteButton(0); - AddStep("click delete button", () => InputManager.Click(MouseButton.Left)); - - AddAssert("item removed", () => !playlist.Items.Contains(selectedItem)); - } - - [Test] - public void TestNextItemSelectedAfterDeletion() - { - createPlaylist(true, true); - - moveToItem(0); - AddStep("click", () => InputManager.Click(MouseButton.Left)); - - moveToDeleteButton(0); - AddStep("click delete button", () => InputManager.Click(MouseButton.Left)); - - AddAssert("item 0 is selected", () => playlist.SelectedItem.Value == playlist.Items[0]); - } - - [Test] - public void TestLastItemSelectedAfterLastItemDeleted() - { - createPlaylist(true, true); - - AddWaitStep("wait for flow", 5); // Items may take 1 update frame to flow. A wait count of 5 is guaranteed to result in the flow being updated as desired. - AddStep("scroll to bottom", () => playlist.ChildrenOfType>().First().ScrollToEnd(false)); - - moveToItem(19); - AddStep("click", () => InputManager.Click(MouseButton.Left)); - - moveToDeleteButton(19); - AddStep("click delete button", () => InputManager.Click(MouseButton.Left)); - - AddAssert("item 18 is selected", () => playlist.SelectedItem.Value == playlist.Items[18]); - } - - [Test] - public void TestSelectionResetWhenAllItemsDeleted() - { - createPlaylist(true, true); - - AddStep("remove all but one item", () => - { - playlist.Items.RemoveRange(1, playlist.Items.Count - 1); - }); - - moveToItem(0); - AddStep("click", () => InputManager.Click(MouseButton.Left)); - moveToDeleteButton(0); - AddStep("click delete button", () => InputManager.Click(MouseButton.Left)); - - AddAssert("no item selected", () => playlist.SelectedItem.Value == null); - } - - // Todo: currently not possible due to bindable list shortcomings (https://github.com/ppy/osu-framework/issues/3081) - // [Test] - public void TestNextItemSelectedAfterExternalDeletion() - { - createPlaylist(true, true); - - moveToItem(0); - AddStep("click", () => InputManager.Click(MouseButton.Left)); - AddStep("remove item 0", () => playlist.Items.RemoveAt(0)); - - AddAssert("item 0 is selected", () => playlist.SelectedItem.Value == playlist.Items[0]); - } - - [Test] - public void TestChangeBeatmapAndRemove() - { - createPlaylist(true, true); - - AddStep("change beatmap of first item", () => playlist.Items[0].BeatmapID = 30); - moveToDeleteButton(0); - AddStep("click delete button", () => InputManager.Click(MouseButton.Left)); - } - [Test] public void TestDownloadButtonHiddenWhenBeatmapExists() { @@ -326,12 +237,6 @@ namespace osu.Game.Tests.Visual.Multiplayer InputManager.MoveMouseTo(item.ChildrenOfType.PlaylistItemHandle>().Single(), offset); }); - private void moveToDeleteButton(int index, Vector2? offset = null) => AddStep($"move mouse to delete button {index}", () => - { - var item = playlist.ChildrenOfType>().ElementAt(index); - InputManager.MoveMouseTo(item.ChildrenOfType().ElementAt(0), offset); - }); - private void assertHandleVisibility(int index, bool visible) => AddAssert($"handle {index} {(visible ? "is" : "is not")} visible", () => (playlist.ChildrenOfType.PlaylistItemHandle>().ElementAt(index).Alpha > 0) == visible); @@ -425,7 +330,7 @@ namespace osu.Game.Tests.Visual.Multiplayer public new IReadOnlyDictionary> ItemMap => base.ItemMap; public TestPlaylist(bool allowEdit, bool allowSelection, bool showItemOwner = false) - : base(allowEdit, allowSelection, showItemOwner: showItemOwner) + : base(allowEdit, allowSelection, showItemOwner) { } } diff --git a/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsRoomPlaylist.cs b/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsRoomPlaylist.cs new file mode 100644 index 0000000000..5b0c7c7d55 --- /dev/null +++ b/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsRoomPlaylist.cs @@ -0,0 +1,188 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using System.Linq; +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Testing; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.Drawables; +using osu.Game.Database; +using osu.Game.Graphics.Containers; +using osu.Game.Online.API.Requests.Responses; +using osu.Game.Online.Rooms; +using osu.Game.Rulesets.Osu; +using osu.Game.Rulesets.Osu.Mods; +using osu.Game.Screens.OnlinePlay; +using osu.Game.Screens.OnlinePlay.Playlists; +using osu.Game.Tests.Beatmaps; +using osuTK; +using osuTK.Input; + +namespace osu.Game.Tests.Visual.Multiplayer +{ + public class TestScenePlaylistsRoomPlaylist : OsuManualInputManagerTestScene + { + private TestPlaylist playlist; + + [Cached(typeof(UserLookupCache))] + private readonly TestUserLookupCache userLookupCache = new TestUserLookupCache(); + + [Test] + public void TestItemRemovedOnDeletion() + { + PlaylistItem selectedItem = null; + + createPlaylist(true, true); + + moveToItem(0); + AddStep("click", () => InputManager.Click(MouseButton.Left)); + AddStep("retrieve selection", () => selectedItem = playlist.SelectedItem.Value); + + moveToDeleteButton(0); + AddStep("click delete button", () => InputManager.Click(MouseButton.Left)); + + AddAssert("item removed", () => !playlist.Items.Contains(selectedItem)); + } + + [Test] + public void TestNextItemSelectedAfterDeletion() + { + createPlaylist(true, true); + + moveToItem(0); + AddStep("click", () => InputManager.Click(MouseButton.Left)); + + moveToDeleteButton(0); + AddStep("click delete button", () => InputManager.Click(MouseButton.Left)); + + AddAssert("item 0 is selected", () => playlist.SelectedItem.Value == playlist.Items[0]); + } + + [Test] + public void TestLastItemSelectedAfterLastItemDeleted() + { + createPlaylist(true, true); + + AddWaitStep("wait for flow", 5); // Items may take 1 update frame to flow. A wait count of 5 is guaranteed to result in the flow being updated as desired. + AddStep("scroll to bottom", () => playlist.ChildrenOfType>().First().ScrollToEnd(false)); + + moveToItem(19); + AddStep("click", () => InputManager.Click(MouseButton.Left)); + + moveToDeleteButton(19); + AddStep("click delete button", () => InputManager.Click(MouseButton.Left)); + + AddAssert("item 18 is selected", () => playlist.SelectedItem.Value == playlist.Items[18]); + } + + [Test] + public void TestSelectionResetWhenAllItemsDeleted() + { + createPlaylist(true, true); + + AddStep("remove all but one item", () => + { + playlist.Items.RemoveRange(1, playlist.Items.Count - 1); + }); + + moveToItem(0); + AddStep("click", () => InputManager.Click(MouseButton.Left)); + moveToDeleteButton(0); + AddStep("click delete button", () => InputManager.Click(MouseButton.Left)); + + AddAssert("no item selected", () => playlist.SelectedItem.Value == null); + } + + // Todo: currently not possible due to bindable list shortcomings (https://github.com/ppy/osu-framework/issues/3081) + // [Test] + public void TestNextItemSelectedAfterExternalDeletion() + { + createPlaylist(true, true); + + moveToItem(0); + AddStep("click", () => InputManager.Click(MouseButton.Left)); + AddStep("remove item 0", () => playlist.Items.RemoveAt(0)); + + AddAssert("item 0 is selected", () => playlist.SelectedItem.Value == playlist.Items[0]); + } + + [Test] + public void TestChangeBeatmapAndRemove() + { + createPlaylist(true, true); + + AddStep("change beatmap of first item", () => playlist.Items[0].BeatmapID = 30); + moveToDeleteButton(0); + AddStep("click delete button", () => InputManager.Click(MouseButton.Left)); + } + + private void moveToItem(int index, Vector2? offset = null) + => AddStep($"move mouse to item {index}", () => InputManager.MoveMouseTo(playlist.ChildrenOfType().ElementAt(index), offset)); + + private void moveToDeleteButton(int index, Vector2? offset = null) => AddStep($"move mouse to delete button {index}", () => + { + var item = playlist.ChildrenOfType>().ElementAt(index); + InputManager.MoveMouseTo(item.ChildrenOfType().ElementAt(0), offset); + }); + + private void createPlaylist(bool allowEdit, bool allowSelection, bool showItemOwner = false) + { + AddStep("create playlist", () => + { + Child = playlist = new TestPlaylist(allowEdit, allowSelection, showItemOwner) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(500, 300) + }; + + for (int i = 0; i < 20; i++) + { + playlist.Items.Add(new PlaylistItem + { + ID = i, + OwnerID = 2, + Beatmap = + { + Value = i % 2 == 1 + ? new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo + : new BeatmapInfo + { + Metadata = new BeatmapMetadata + { + Artist = "Artist", + Author = new APIUser { Username = "Creator name here" }, + Title = "Long title used to check background colour", + }, + BeatmapSet = new BeatmapSetInfo() + } + }, + Ruleset = { Value = new OsuRuleset().RulesetInfo }, + RequiredMods = + { + new OsuModHardRock(), + new OsuModDoubleTime(), + new OsuModAutoplay() + } + }); + } + }); + + AddUntilStep("wait for items to load", () => playlist.ItemMap.Values.All(i => i.IsLoaded)); + } + + private class TestPlaylist : PlaylistsRoomPlaylist + { + public new IReadOnlyDictionary> ItemMap => base.ItemMap; + + public TestPlaylist(bool allowEdit, bool allowSelection, bool showItemOwner = false) + : base(allowEdit, allowSelection, showItemOwner) + { + } + } + } +} diff --git a/osu.Game/Screens/OnlinePlay/Components/MatchBeatmapDetailArea.cs b/osu.Game/Screens/OnlinePlay/Components/MatchBeatmapDetailArea.cs index b013cbafd8..7afebb04af 100644 --- a/osu.Game/Screens/OnlinePlay/Components/MatchBeatmapDetailArea.cs +++ b/osu.Game/Screens/OnlinePlay/Components/MatchBeatmapDetailArea.cs @@ -9,6 +9,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Graphics.UserInterface; using osu.Game.Online.Rooms; +using osu.Game.Screens.OnlinePlay.Playlists; using osu.Game.Screens.Select; using osuTK; @@ -43,7 +44,7 @@ namespace osu.Game.Screens.OnlinePlay.Components { RelativeSizeAxes = Axes.Both, Padding = new MarginPadding { Bottom = 10 }, - Child = playlist = new DrawableRoomPlaylist(true, false) + Child = playlist = new PlaylistsRoomPlaylist(true, false) { RelativeSizeAxes = Axes.Both, } diff --git a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylist.cs b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylist.cs index f2d31c8e67..35d1fb33ad 100644 --- a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylist.cs +++ b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylist.cs @@ -1,9 +1,8 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System.Collections.Specialized; +using System; using osu.Framework.Bindables; -using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Graphics.Containers; @@ -16,6 +15,11 @@ namespace osu.Game.Screens.OnlinePlay { public readonly Bindable SelectedItem = new Bindable(); + /// + /// Invoked when an item is requested to be deleted. + /// + public Action DeletionRequested; + private readonly bool allowEdit; private readonly bool allowSelection; private readonly bool showItemOwner; @@ -27,23 +31,6 @@ namespace osu.Game.Screens.OnlinePlay this.showItemOwner = showItemOwner; } - protected override void LoadComplete() - { - base.LoadComplete(); - - // Scheduled since items are removed and re-added upon rearrangement - Items.CollectionChanged += (_, args) => Schedule(() => - { - switch (args.Action) - { - case NotifyCollectionChangedAction.Remove: - if (allowSelection && args.OldItems.Contains(SelectedItem)) - SelectedItem.Value = null; - break; - } - }); - } - protected override ScrollContainer CreateScrollContainer() => base.CreateScrollContainer().With(d => { d.ScrollbarVisible = false; @@ -57,20 +44,7 @@ namespace osu.Game.Screens.OnlinePlay protected override OsuRearrangeableListItem CreateOsuDrawable(PlaylistItem item) => new DrawableRoomPlaylistItem(item, allowEdit, allowSelection, showItemOwner) { SelectedItem = { BindTarget = SelectedItem }, - RequestDeletion = requestDeletion + RequestDeletion = i => DeletionRequested?.Invoke(i) }; - - private void requestDeletion(PlaylistItem item) - { - if (allowSelection && SelectedItem.Value == item) - { - if (Items.Count == 1) - SelectedItem.Value = null; - else - SelectedItem.Value = Items.GetNext(item) ?? Items[^2]; - } - - Items.Remove(item); - } } } diff --git a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistWithResults.cs b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistWithResults.cs index 8b1bb7abc1..1acd239fc8 100644 --- a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistWithResults.cs +++ b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistWithResults.cs @@ -22,7 +22,7 @@ namespace osu.Game.Screens.OnlinePlay private readonly bool showItemOwner; public DrawableRoomPlaylistWithResults(bool showItemOwner = false) - : base(false, true, showItemOwner: showItemOwner) + : base(false, true, showItemOwner) { this.showItemOwner = showItemOwner; } diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomPlaylist.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomPlaylist.cs new file mode 100644 index 0000000000..de0960940d --- /dev/null +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomPlaylist.cs @@ -0,0 +1,24 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Linq; +using osu.Framework.Extensions.IEnumerableExtensions; + +namespace osu.Game.Screens.OnlinePlay.Playlists +{ + public class PlaylistsRoomPlaylist : DrawableRoomPlaylist + { + public PlaylistsRoomPlaylist(bool allowEdit, bool allowSelection, bool showItemOwner = false) + : base(allowEdit, allowSelection, showItemOwner) + { + DeletionRequested = item => + { + var nextItem = Items.GetNext(item); + + Items.Remove(item); + + SelectedItem.Value = nextItem ?? Items.LastOrDefault(); + }; + } + } +} diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSettingsOverlay.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSettingsOverlay.cs index 27c8dc1120..b903e9cb7b 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSettingsOverlay.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSettingsOverlay.cs @@ -205,7 +205,10 @@ namespace osu.Game.Screens.OnlinePlay.Playlists { new Drawable[] { - playlist = new DrawableRoomPlaylist(true, false) { RelativeSizeAxes = Axes.Both } + playlist = new PlaylistsRoomPlaylist(true, false) + { + RelativeSizeAxes = Axes.Both + } }, new Drawable[] { From 3be4d8b68de9ac9c5286917dd85be79470849920 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 9 Dec 2021 01:04:28 +0900 Subject: [PATCH 026/146] Remove ctor params from DrawableRoomPlaylist/DrawablePlaylistItem --- .../TestSceneDrawableRoomPlaylist.cs | 18 ++++- .../TestScenePlaylistsRoomPlaylist.cs | 20 +++--- .../Components/MatchBeatmapDetailArea.cs | 2 +- .../OnlinePlay/DrawableRoomPlaylist.cs | 14 +--- .../OnlinePlay/DrawableRoomPlaylistItem.cs | 66 ++++++++++++------- .../Match/MultiplayerMatchSettingsOverlay.cs | 2 +- .../Match/Playlist/MultiplayerHistoryList.cs | 9 ++- .../Match/Playlist/MultiplayerQueueList.cs | 9 ++- .../Playlists/PlaylistsRoomPlaylist.cs | 23 ++++++- .../Playlists/PlaylistsRoomSettingsOverlay.cs | 2 +- 10 files changed, 105 insertions(+), 60 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs index c60b55d7e8..449fdaf0aa 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs @@ -329,10 +329,26 @@ namespace osu.Game.Tests.Visual.Multiplayer { public new IReadOnlyDictionary> ItemMap => base.ItemMap; + private readonly bool allowEdit; + private readonly bool allowSelection; + private readonly bool showItemOwner; + public TestPlaylist(bool allowEdit, bool allowSelection, bool showItemOwner = false) - : base(allowEdit, allowSelection, showItemOwner) { + this.allowEdit = allowEdit; + this.allowSelection = allowSelection; + this.showItemOwner = showItemOwner; } + + protected override OsuRearrangeableListItem CreateOsuDrawable(PlaylistItem item) => base.CreateOsuDrawable(item).With(d => + { + var drawablePlaylistItem = (DrawableRoomPlaylistItem)d; + + drawablePlaylistItem.AllowReordering = allowEdit; + drawablePlaylistItem.AllowDeletion = allowEdit; + drawablePlaylistItem.AllowSelection = allowSelection; + drawablePlaylistItem.ShowItemOwner = showItemOwner; + }); } } } diff --git a/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsRoomPlaylist.cs b/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsRoomPlaylist.cs index 5b0c7c7d55..8312964b50 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsRoomPlaylist.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsRoomPlaylist.cs @@ -36,7 +36,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { PlaylistItem selectedItem = null; - createPlaylist(true, true); + createPlaylist(); moveToItem(0); AddStep("click", () => InputManager.Click(MouseButton.Left)); @@ -51,7 +51,7 @@ namespace osu.Game.Tests.Visual.Multiplayer [Test] public void TestNextItemSelectedAfterDeletion() { - createPlaylist(true, true); + createPlaylist(); moveToItem(0); AddStep("click", () => InputManager.Click(MouseButton.Left)); @@ -65,7 +65,7 @@ namespace osu.Game.Tests.Visual.Multiplayer [Test] public void TestLastItemSelectedAfterLastItemDeleted() { - createPlaylist(true, true); + createPlaylist(); AddWaitStep("wait for flow", 5); // Items may take 1 update frame to flow. A wait count of 5 is guaranteed to result in the flow being updated as desired. AddStep("scroll to bottom", () => playlist.ChildrenOfType>().First().ScrollToEnd(false)); @@ -82,7 +82,7 @@ namespace osu.Game.Tests.Visual.Multiplayer [Test] public void TestSelectionResetWhenAllItemsDeleted() { - createPlaylist(true, true); + createPlaylist(); AddStep("remove all but one item", () => { @@ -101,7 +101,7 @@ namespace osu.Game.Tests.Visual.Multiplayer // [Test] public void TestNextItemSelectedAfterExternalDeletion() { - createPlaylist(true, true); + createPlaylist(); moveToItem(0); AddStep("click", () => InputManager.Click(MouseButton.Left)); @@ -113,7 +113,7 @@ namespace osu.Game.Tests.Visual.Multiplayer [Test] public void TestChangeBeatmapAndRemove() { - createPlaylist(true, true); + createPlaylist(); AddStep("change beatmap of first item", () => playlist.Items[0].BeatmapID = 30); moveToDeleteButton(0); @@ -129,11 +129,11 @@ namespace osu.Game.Tests.Visual.Multiplayer InputManager.MoveMouseTo(item.ChildrenOfType().ElementAt(0), offset); }); - private void createPlaylist(bool allowEdit, bool allowSelection, bool showItemOwner = false) + private void createPlaylist() { AddStep("create playlist", () => { - Child = playlist = new TestPlaylist(allowEdit, allowSelection, showItemOwner) + Child = playlist = new TestPlaylist { Anchor = Anchor.Centre, Origin = Anchor.Centre, @@ -179,8 +179,8 @@ namespace osu.Game.Tests.Visual.Multiplayer { public new IReadOnlyDictionary> ItemMap => base.ItemMap; - public TestPlaylist(bool allowEdit, bool allowSelection, bool showItemOwner = false) - : base(allowEdit, allowSelection, showItemOwner) + public TestPlaylist() + : base(true, true, true) { } } diff --git a/osu.Game/Screens/OnlinePlay/Components/MatchBeatmapDetailArea.cs b/osu.Game/Screens/OnlinePlay/Components/MatchBeatmapDetailArea.cs index 7afebb04af..761f4818e0 100644 --- a/osu.Game/Screens/OnlinePlay/Components/MatchBeatmapDetailArea.cs +++ b/osu.Game/Screens/OnlinePlay/Components/MatchBeatmapDetailArea.cs @@ -44,7 +44,7 @@ namespace osu.Game.Screens.OnlinePlay.Components { RelativeSizeAxes = Axes.Both, Padding = new MarginPadding { Bottom = 10 }, - Child = playlist = new PlaylistsRoomPlaylist(true, false) + Child = playlist = new PlaylistsRoomPlaylist(true, true, true) { RelativeSizeAxes = Axes.Both, } diff --git a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylist.cs b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylist.cs index 35d1fb33ad..2eb8cd8997 100644 --- a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylist.cs +++ b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylist.cs @@ -20,16 +20,6 @@ namespace osu.Game.Screens.OnlinePlay /// public Action DeletionRequested; - private readonly bool allowEdit; - private readonly bool allowSelection; - private readonly bool showItemOwner; - - public DrawableRoomPlaylist(bool allowEdit, bool allowSelection, bool showItemOwner = false) - { - this.allowEdit = allowEdit; - this.allowSelection = allowSelection; - this.showItemOwner = showItemOwner; - } protected override ScrollContainer CreateScrollContainer() => base.CreateScrollContainer().With(d => { @@ -41,10 +31,10 @@ namespace osu.Game.Screens.OnlinePlay Spacing = new Vector2(0, 2) }; - protected override OsuRearrangeableListItem CreateOsuDrawable(PlaylistItem item) => new DrawableRoomPlaylistItem(item, allowEdit, allowSelection, showItemOwner) + protected override OsuRearrangeableListItem CreateOsuDrawable(PlaylistItem item) => new DrawableRoomPlaylistItem(item) { SelectedItem = { BindTarget = SelectedItem }, - RequestDeletion = i => DeletionRequested?.Invoke(i) + RequestDeletion = i => DeletionRequested?.Invoke(i), }; } } diff --git a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs index c291bddeeb..1814e172df 100644 --- a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs +++ b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs @@ -7,7 +7,6 @@ using System.Linq; using System.Threading.Tasks; using osu.Framework.Allocation; using osu.Framework.Bindables; -using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; @@ -53,6 +52,7 @@ namespace osu.Game.Screens.OnlinePlay private ModDisplay modDisplay; private FillFlowContainer buttonsFlow; private UpdateableAvatar ownerAvatar; + private Drawable removeButton; private readonly IBindable valid = new Bindable(); @@ -75,31 +75,20 @@ namespace osu.Game.Screens.OnlinePlay private readonly DelayedLoadWrapper onScreenLoader = new DelayedLoadWrapper(Empty) { RelativeSizeAxes = Axes.Both }; - private readonly bool allowEdit; - private readonly bool allowSelection; - private readonly bool showItemOwner; - private FillFlowContainer mainFillFlow; - protected override bool ShouldBeConsideredForInput(Drawable child) => allowEdit || !allowSelection || SelectedItem.Value == Model; + protected override bool ShouldBeConsideredForInput(Drawable child) => AllowReordering || AllowDeletion || !AllowSelection || SelectedItem.Value == Model; - public DrawableRoomPlaylistItem(PlaylistItem item, bool allowEdit, bool allowSelection, bool showItemOwner) + public DrawableRoomPlaylistItem(PlaylistItem item) : base(item) { Item = item; - // TODO: edit support should be moved out into a derived class - this.allowEdit = allowEdit; - this.allowSelection = allowSelection; - this.showItemOwner = showItemOwner; - beatmap.BindTo(item.Beatmap); valid.BindTo(item.Valid); ruleset.BindTo(item.Ruleset); requiredMods.BindTo(item.RequiredMods); - ShowDragHandle.Value = allowEdit; - if (item.Expired) Colour = OsuColour.Gray(0.5f); } @@ -107,9 +96,6 @@ namespace osu.Game.Screens.OnlinePlay [BackgroundDependencyLoader] private void load() { - if (!allowEdit) - HandleColour = HandleColour.Opacity(0); - maskingContainer.BorderColour = colours.Yellow; } @@ -169,6 +155,42 @@ namespace osu.Game.Screens.OnlinePlay refresh(); } + public bool AllowSelection { get; set; } + + public bool AllowReordering + { + get => ShowDragHandle.Value; + set => ShowDragHandle.Value = value; + } + + private bool allowDeletion; + + public bool AllowDeletion + { + get => allowDeletion; + set + { + allowDeletion = value; + + if (removeButton != null) + removeButton.Alpha = value ? 1 : 0; + } + } + + private bool showItemOwner; + + public bool ShowItemOwner + { + get => showItemOwner; + set + { + showItemOwner = value; + + if (ownerAvatar != null) + ownerAvatar.Alpha = value ? 1 : 0; + } + } + private void refresh() { if (!valid.Value) @@ -336,7 +358,7 @@ namespace osu.Game.Screens.OnlinePlay Margin = new MarginPadding { Right = 8 }, Masking = true, CornerRadius = 4, - Alpha = showItemOwner ? 1 : 0 + Alpha = ShowItemOwner ? 1 : 0 }, } } @@ -349,11 +371,11 @@ namespace osu.Game.Screens.OnlinePlay new[] { Item.Beatmap.Value == null ? Empty() : new PlaylistDownloadButton(Item), - new PlaylistRemoveButton + removeButton = new PlaylistRemoveButton { Size = new Vector2(30, 30), - Alpha = allowEdit ? 1 : 0, - Action = () => RequestDeletion?.Invoke(Model), + Alpha = AllowDeletion ? 1 : 0, + Action = () => RequestDeletion?.Invoke(Item), }, }; @@ -374,7 +396,7 @@ namespace osu.Game.Screens.OnlinePlay protected override bool OnClick(ClickEvent e) { - if (allowSelection && valid.Value) + if (AllowSelection && valid.Value) SelectedItem.Value = Model; return true; } diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs index 39d60a0b05..7f1db733b3 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs @@ -248,7 +248,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match Spacing = new Vector2(5), Children = new Drawable[] { - drawablePlaylist = new DrawableRoomPlaylist(false, false) + drawablePlaylist = new DrawableRoomPlaylist { RelativeSizeAxes = Axes.X, Height = DrawableRoomPlaylistItem.HEIGHT diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerHistoryList.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerHistoryList.cs index d708b39898..7102738271 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerHistoryList.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerHistoryList.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.Linq; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Game.Graphics.Containers; using osu.Game.Online.Rooms; using osuTK; @@ -15,16 +16,14 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match.Playlist /// public class MultiplayerHistoryList : DrawableRoomPlaylist { - public MultiplayerHistoryList() - : base(false, false, true) - { - } - protected override FillFlowContainer> CreateListFillFlowContainer() => new HistoryFillFlowContainer { Spacing = new Vector2(0, 2) }; + protected override OsuRearrangeableListItem CreateOsuDrawable(PlaylistItem item) + => base.CreateOsuDrawable(item).With(d => ((DrawableRoomPlaylistItem)d).ShowItemOwner = true); + private class HistoryFillFlowContainer : FillFlowContainer> { public override IEnumerable FlowingChildren => base.FlowingChildren.OfType>().OrderByDescending(item => item.Model.PlayedAt); diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerQueueList.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerQueueList.cs index 1b1b66273f..814ea48646 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerQueueList.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerQueueList.cs @@ -7,6 +7,7 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Game.Graphics.Containers; using osu.Game.Online.Rooms; using osuTK; @@ -17,16 +18,14 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match.Playlist /// public class MultiplayerQueueList : DrawableRoomPlaylist { - public MultiplayerQueueList() - : base(false, false, true) - { - } - protected override FillFlowContainer> CreateListFillFlowContainer() => new QueueFillFlowContainer { Spacing = new Vector2(0, 2) }; + protected override OsuRearrangeableListItem CreateOsuDrawable(PlaylistItem item) + => base.CreateOsuDrawable(item).With(d => ((DrawableRoomPlaylistItem)d).ShowItemOwner = true); + private class QueueFillFlowContainer : FillFlowContainer> { [Resolved(typeof(Room), nameof(Room.Playlist))] diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomPlaylist.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomPlaylist.cs index de0960940d..f4df9c4406 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomPlaylist.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomPlaylist.cs @@ -3,14 +3,24 @@ using System.Linq; using osu.Framework.Extensions.IEnumerableExtensions; +using osu.Framework.Graphics; +using osu.Game.Graphics.Containers; +using osu.Game.Online.Rooms; namespace osu.Game.Screens.OnlinePlay.Playlists { public class PlaylistsRoomPlaylist : DrawableRoomPlaylist { - public PlaylistsRoomPlaylist(bool allowEdit, bool allowSelection, bool showItemOwner = false) - : base(allowEdit, allowSelection, showItemOwner) + private readonly bool allowReordering; + private readonly bool allowDeletion; + private readonly bool allowSelection; + + public PlaylistsRoomPlaylist(bool allowReordering, bool allowDeletion, bool allowSelection) { + this.allowReordering = allowReordering; + this.allowDeletion = allowDeletion; + this.allowSelection = allowSelection; + DeletionRequested = item => { var nextItem = Items.GetNext(item); @@ -20,5 +30,14 @@ namespace osu.Game.Screens.OnlinePlay.Playlists SelectedItem.Value = nextItem ?? Items.LastOrDefault(); }; } + + protected override OsuRearrangeableListItem CreateOsuDrawable(PlaylistItem item) => base.CreateOsuDrawable(item).With(d => + { + var drawablePlaylistItem = (DrawableRoomPlaylistItem)d; + + drawablePlaylistItem.AllowReordering = allowReordering; + drawablePlaylistItem.AllowDeletion = allowDeletion; + drawablePlaylistItem.AllowSelection = allowSelection; + }); } } diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSettingsOverlay.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSettingsOverlay.cs index b903e9cb7b..40b0bc7571 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSettingsOverlay.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSettingsOverlay.cs @@ -205,7 +205,7 @@ namespace osu.Game.Screens.OnlinePlay.Playlists { new Drawable[] { - playlist = new PlaylistsRoomPlaylist(true, false) + playlist = new PlaylistsRoomPlaylist(true, true, false) { RelativeSizeAxes = Axes.Both } From 26f6c5e5a5571469a810b54a68049da8a74461e2 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 9 Dec 2021 01:16:37 +0900 Subject: [PATCH 027/146] Remove ctor params from PlaylistsRoomPlaylist --- .../TestSceneDrawableRoomPlaylist.cs | 44 ++++---------- .../TestScenePlaylistsRoomPlaylist.cs | 2 +- .../Components/MatchBeatmapDetailArea.cs | 3 +- .../OnlinePlay/DrawableRoomPlaylist.cs | 60 +++++++++++++++++++ .../Playlists/PlaylistsRoomPlaylist.cs | 23 +------ .../Playlists/PlaylistsRoomSettingsOverlay.cs | 4 +- 6 files changed, 80 insertions(+), 56 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs index 449fdaf0aa..269cd15a0f 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; @@ -48,7 +49,7 @@ namespace osu.Game.Tests.Visual.Multiplayer [Test] public void TestNonEditableNonSelectable() { - createPlaylist(false, false); + createPlaylist(); moveToItem(0); assertHandleVisibility(0, false); @@ -61,7 +62,7 @@ namespace osu.Game.Tests.Visual.Multiplayer [Test] public void TestEditable() { - createPlaylist(true, false); + createPlaylist(p => p.AllowReordering = p.AllowDeletion = true); moveToItem(0); assertHandleVisibility(0, true); @@ -74,7 +75,7 @@ namespace osu.Game.Tests.Visual.Multiplayer [Test] public void TestMarkInvalid() { - createPlaylist(true, true); + createPlaylist(p => p.AllowReordering = p.AllowDeletion = p.AllowSelection = true); AddStep("mark item 0 as invalid", () => playlist.Items[0].MarkInvalid()); @@ -87,7 +88,7 @@ namespace osu.Game.Tests.Visual.Multiplayer [Test] public void TestSelectable() { - createPlaylist(false, true); + createPlaylist(p => p.AllowSelection = true); moveToItem(0); assertHandleVisibility(0, false); @@ -101,7 +102,7 @@ namespace osu.Game.Tests.Visual.Multiplayer [Test] public void TestEditableSelectable() { - createPlaylist(true, true); + createPlaylist(p => p.AllowReordering = p.AllowDeletion = p.AllowSelection = true); moveToItem(0); assertHandleVisibility(0, true); @@ -115,7 +116,7 @@ namespace osu.Game.Tests.Visual.Multiplayer [Test] public void TestSelectionNotLostAfterRearrangement() { - createPlaylist(true, true); + createPlaylist(p => p.AllowReordering = p.AllowDeletion = p.AllowSelection = true); moveToItem(0); AddStep("click", () => InputManager.Click(MouseButton.Left)); @@ -180,7 +181,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { AddStep("create playlist", () => { - Child = playlist = new TestPlaylist(false, false) + Child = playlist = new TestPlaylist { Anchor = Anchor.Centre, Origin = Anchor.Centre, @@ -223,7 +224,7 @@ namespace osu.Game.Tests.Visual.Multiplayer [TestCase(true)] public void TestWithOwner(bool withOwner) { - createPlaylist(false, false, withOwner); + createPlaylist(p => p.ShowItemOwners = withOwner); AddAssert("owner visible", () => playlist.ChildrenOfType().All(a => a.IsPresent == withOwner)); } @@ -245,11 +246,11 @@ namespace osu.Game.Tests.Visual.Multiplayer => AddAssert($"delete button {index} {(visible ? "is" : "is not")} visible", () => (playlist.ChildrenOfType().ElementAt(2 + index * 2).Alpha > 0) == visible); - private void createPlaylist(bool allowEdit, bool allowSelection, bool showItemOwner = false) + private void createPlaylist(Action setupPlaylist) { AddStep("create playlist", () => { - Child = playlist = new TestPlaylist(allowEdit, allowSelection, showItemOwner) + Child = playlist = new TestPlaylist { Anchor = Anchor.Centre, Origin = Anchor.Centre, @@ -295,7 +296,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { AddStep("create playlist", () => { - Child = playlist = new TestPlaylist(false, false) + Child = playlist = new TestPlaylist { Anchor = Anchor.Centre, Origin = Anchor.Centre, @@ -328,27 +329,6 @@ namespace osu.Game.Tests.Visual.Multiplayer private class TestPlaylist : DrawableRoomPlaylist { public new IReadOnlyDictionary> ItemMap => base.ItemMap; - - private readonly bool allowEdit; - private readonly bool allowSelection; - private readonly bool showItemOwner; - - public TestPlaylist(bool allowEdit, bool allowSelection, bool showItemOwner = false) - { - this.allowEdit = allowEdit; - this.allowSelection = allowSelection; - this.showItemOwner = showItemOwner; - } - - protected override OsuRearrangeableListItem CreateOsuDrawable(PlaylistItem item) => base.CreateOsuDrawable(item).With(d => - { - var drawablePlaylistItem = (DrawableRoomPlaylistItem)d; - - drawablePlaylistItem.AllowReordering = allowEdit; - drawablePlaylistItem.AllowDeletion = allowEdit; - drawablePlaylistItem.AllowSelection = allowSelection; - drawablePlaylistItem.ShowItemOwner = showItemOwner; - }); } } } diff --git a/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsRoomPlaylist.cs b/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsRoomPlaylist.cs index 8312964b50..264f6aa2c5 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsRoomPlaylist.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsRoomPlaylist.cs @@ -180,8 +180,8 @@ namespace osu.Game.Tests.Visual.Multiplayer public new IReadOnlyDictionary> ItemMap => base.ItemMap; public TestPlaylist() - : base(true, true, true) { + AllowSelection = true; } } } diff --git a/osu.Game/Screens/OnlinePlay/Components/MatchBeatmapDetailArea.cs b/osu.Game/Screens/OnlinePlay/Components/MatchBeatmapDetailArea.cs index 761f4818e0..d56acff8c7 100644 --- a/osu.Game/Screens/OnlinePlay/Components/MatchBeatmapDetailArea.cs +++ b/osu.Game/Screens/OnlinePlay/Components/MatchBeatmapDetailArea.cs @@ -44,9 +44,10 @@ namespace osu.Game.Screens.OnlinePlay.Components { RelativeSizeAxes = Axes.Both, Padding = new MarginPadding { Bottom = 10 }, - Child = playlist = new PlaylistsRoomPlaylist(true, true, true) + Child = playlist = new PlaylistsRoomPlaylist { RelativeSizeAxes = Axes.Both, + AllowSelection = true, } } }, diff --git a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylist.cs b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylist.cs index 2eb8cd8997..4389f40afc 100644 --- a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylist.cs +++ b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylist.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Linq; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -20,6 +21,61 @@ namespace osu.Game.Screens.OnlinePlay /// public Action DeletionRequested; + private bool allowReordering; + + public bool AllowReordering + { + get => allowReordering; + set + { + allowReordering = value; + + foreach (var item in ListContainer.OfType()) + item.AllowReordering = value; + } + } + + private bool allowDeletion; + + public bool AllowDeletion + { + get => allowDeletion; + set + { + allowDeletion = value; + + foreach (var item in ListContainer.OfType()) + item.AllowDeletion = value; + } + } + + private bool allowSelection; + + public bool AllowSelection + { + get => allowSelection; + set + { + allowSelection = value; + + foreach (var item in ListContainer.OfType()) + item.AllowSelection = value; + } + } + + private bool showItemOwners; + + public bool ShowItemOwners + { + get => showItemOwners; + set + { + showItemOwners = value; + + foreach (var item in ListContainer.OfType()) + item.ShowItemOwner = value; + } + } protected override ScrollContainer CreateScrollContainer() => base.CreateScrollContainer().With(d => { @@ -35,6 +91,10 @@ namespace osu.Game.Screens.OnlinePlay { SelectedItem = { BindTarget = SelectedItem }, RequestDeletion = i => DeletionRequested?.Invoke(i), + AllowReordering = AllowReordering, + AllowDeletion = AllowDeletion, + AllowSelection = AllowSelection, + ShowItemOwner = ShowItemOwners, }; } } diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomPlaylist.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomPlaylist.cs index f4df9c4406..d8c316b642 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomPlaylist.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomPlaylist.cs @@ -3,23 +3,15 @@ using System.Linq; using osu.Framework.Extensions.IEnumerableExtensions; -using osu.Framework.Graphics; -using osu.Game.Graphics.Containers; -using osu.Game.Online.Rooms; namespace osu.Game.Screens.OnlinePlay.Playlists { public class PlaylistsRoomPlaylist : DrawableRoomPlaylist { - private readonly bool allowReordering; - private readonly bool allowDeletion; - private readonly bool allowSelection; - - public PlaylistsRoomPlaylist(bool allowReordering, bool allowDeletion, bool allowSelection) + public PlaylistsRoomPlaylist() { - this.allowReordering = allowReordering; - this.allowDeletion = allowDeletion; - this.allowSelection = allowSelection; + AllowReordering = true; + AllowDeletion = true; DeletionRequested = item => { @@ -30,14 +22,5 @@ namespace osu.Game.Screens.OnlinePlay.Playlists SelectedItem.Value = nextItem ?? Items.LastOrDefault(); }; } - - protected override OsuRearrangeableListItem CreateOsuDrawable(PlaylistItem item) => base.CreateOsuDrawable(item).With(d => - { - var drawablePlaylistItem = (DrawableRoomPlaylistItem)d; - - drawablePlaylistItem.AllowReordering = allowReordering; - drawablePlaylistItem.AllowDeletion = allowDeletion; - drawablePlaylistItem.AllowSelection = allowSelection; - }); } } diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSettingsOverlay.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSettingsOverlay.cs index 40b0bc7571..915ec356d5 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSettingsOverlay.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSettingsOverlay.cs @@ -205,9 +205,9 @@ namespace osu.Game.Screens.OnlinePlay.Playlists { new Drawable[] { - playlist = new PlaylistsRoomPlaylist(true, true, false) + playlist = new PlaylistsRoomPlaylist { - RelativeSizeAxes = Axes.Both + RelativeSizeAxes = Axes.Both, } }, new Drawable[] From be2dbf42c3a0f69b1487522676a8a0ba29c3e987 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 9 Dec 2021 01:17:25 +0900 Subject: [PATCH 028/146] Flatten DrawableRoomPlaylistWithResults into base class --- .../OnlinePlay/DrawableRoomPlaylist.cs | 21 ++++++ .../OnlinePlay/DrawableRoomPlaylistItem.cs | 44 +++++++++++- .../DrawableRoomPlaylistWithResults.cs | 69 ------------------- .../Playlists/PlaylistsRoomSubScreen.cs | 6 +- 4 files changed, 66 insertions(+), 74 deletions(-) delete mode 100644 osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistWithResults.cs diff --git a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylist.cs b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylist.cs index 4389f40afc..e76d905849 100644 --- a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylist.cs +++ b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylist.cs @@ -21,6 +21,11 @@ namespace osu.Game.Screens.OnlinePlay /// public Action DeletionRequested; + /// + /// Invoked to request showing the results for an item. + /// + public Action ShowResultsRequested; + private bool allowReordering; public bool AllowReordering @@ -63,6 +68,20 @@ namespace osu.Game.Screens.OnlinePlay } } + private bool allowShowingResults; + + public bool AllowShowingResults + { + get => allowShowingResults; + set + { + allowShowingResults = value; + + foreach (var item in ListContainer.OfType()) + item.AllowShowingResults = value; + } + } + private bool showItemOwners; public bool ShowItemOwners @@ -94,7 +113,9 @@ namespace osu.Game.Screens.OnlinePlay AllowReordering = AllowReordering, AllowDeletion = AllowDeletion, AllowSelection = AllowSelection, + AllowShowingResults = AllowShowingResults, ShowItemOwner = ShowItemOwners, + ShowResultsRequested = i => ShowResultsRequested?.Invoke(i) }; } } diff --git a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs index 1814e172df..28b4997b63 100644 --- a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs +++ b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs @@ -41,6 +41,7 @@ namespace osu.Game.Screens.OnlinePlay public const float ICON_HEIGHT = 34; public Action RequestDeletion; + public Action ShowResultsRequested; public readonly Bindable SelectedItem = new Bindable(); @@ -53,6 +54,7 @@ namespace osu.Game.Screens.OnlinePlay private FillFlowContainer buttonsFlow; private UpdateableAvatar ownerAvatar; private Drawable removeButton; + private Drawable showResultsButton; private readonly IBindable valid = new Bindable(); @@ -177,6 +179,20 @@ namespace osu.Game.Screens.OnlinePlay } } + private bool allowShowingResults; + + public bool AllowShowingResults + { + get => allowShowingResults; + set + { + allowShowingResults = value; + + if (showResultsButton != null) + showResultsButton.Alpha = value ? 1 : 0; + } + } + private bool showItemOwner; public bool ShowItemOwner @@ -230,7 +246,7 @@ namespace osu.Game.Screens.OnlinePlay modDisplay.Current.Value = requiredMods.ToArray(); buttonsFlow.Clear(); - buttonsFlow.ChildrenEnumerable = CreateButtons(); + buttonsFlow.ChildrenEnumerable = createButtons(); difficultyIconContainer.FadeInFromZero(500, Easing.OutQuint); mainFillFlow.FadeInFromZero(500, Easing.OutQuint); @@ -344,7 +360,7 @@ namespace osu.Game.Screens.OnlinePlay Margin = new MarginPadding { Horizontal = 8 }, AutoSizeAxes = Axes.Both, Spacing = new Vector2(5), - ChildrenEnumerable = CreateButtons().Select(button => button.With(b => + ChildrenEnumerable = createButtons().Select(button => button.With(b => { b.Anchor = Anchor.Centre; b.Origin = Anchor.Centre; @@ -367,9 +383,14 @@ namespace osu.Game.Screens.OnlinePlay }; } - protected virtual IEnumerable CreateButtons() => + private IEnumerable createButtons() => new[] { + showResultsButton = new ShowResultsButton + { + Action = () => ShowResultsRequested?.Invoke(Item), + Alpha = AllowShowingResults ? 1 : 0, + }, Item.Beatmap.Value == null ? Empty() : new PlaylistDownloadButton(Item), removeButton = new PlaylistRemoveButton { @@ -454,6 +475,23 @@ namespace osu.Game.Screens.OnlinePlay } } + private class ShowResultsButton : IconButton + { + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + Icon = FontAwesome.Solid.ChartPie; + TooltipText = "View results"; + + Add(new Box + { + RelativeSizeAxes = Axes.Both, + Depth = float.MaxValue, + Colour = colours.Gray4, + }); + } + } + // For now, this is the same implementation as in PanelBackground, but supports a beatmap info rather than a working beatmap private class PanelBackground : Container // todo: should be a buffered container (https://github.com/ppy/osu-framework/issues/3222) { diff --git a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistWithResults.cs b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistWithResults.cs deleted file mode 100644 index 1acd239fc8..0000000000 --- a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistWithResults.cs +++ /dev/null @@ -1,69 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using System; -using System.Collections.Generic; -using System.Linq; -using osu.Framework.Allocation; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Shapes; -using osu.Framework.Graphics.Sprites; -using osu.Game.Graphics; -using osu.Game.Graphics.Containers; -using osu.Game.Graphics.UserInterface; -using osu.Game.Online.Rooms; - -namespace osu.Game.Screens.OnlinePlay -{ - public class DrawableRoomPlaylistWithResults : DrawableRoomPlaylist - { - public Action RequestShowResults; - - private readonly bool showItemOwner; - - public DrawableRoomPlaylistWithResults(bool showItemOwner = false) - : base(false, true, showItemOwner) - { - this.showItemOwner = showItemOwner; - } - - protected override OsuRearrangeableListItem CreateOsuDrawable(PlaylistItem item) => - new DrawableRoomPlaylistItemWithResults(item, false, true, showItemOwner) - { - RequestShowResults = () => RequestShowResults(item), - SelectedItem = { BindTarget = SelectedItem }, - }; - - private class DrawableRoomPlaylistItemWithResults : DrawableRoomPlaylistItem - { - public Action RequestShowResults; - - public DrawableRoomPlaylistItemWithResults(PlaylistItem item, bool allowEdit, bool allowSelection, bool showItemOwner) - : base(item, allowEdit, allowSelection, showItemOwner) - { - } - - protected override IEnumerable CreateButtons() => - base.CreateButtons().Prepend(new FilledIconButton - { - Icon = FontAwesome.Solid.ChartPie, - Action = () => RequestShowResults?.Invoke(), - TooltipText = "View results" - }); - - private class FilledIconButton : IconButton - { - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - Add(new Box - { - RelativeSizeAxes = Axes.Both, - Depth = float.MaxValue, - Colour = colours.Gray4, - }); - } - } - } - } -} diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs index 7e045802f7..dd2d22b742 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs @@ -88,12 +88,14 @@ namespace osu.Game.Screens.OnlinePlay.Playlists new Drawable[] { new OverlinedPlaylistHeader(), }, new Drawable[] { - new DrawableRoomPlaylistWithResults + new DrawableRoomPlaylist { RelativeSizeAxes = Axes.Both, Items = { BindTarget = Room.Playlist }, SelectedItem = { BindTarget = SelectedItem }, - RequestShowResults = item => + AllowSelection = true, + AllowShowingResults = true, + ShowResultsRequested = item => { Debug.Assert(RoomId.Value != null); ParentScreen?.Push(new PlaylistsResultsScreen(null, RoomId.Value.Value, item, false)); From 3b4833ca8eba289957601cab070d86703a74d1ae Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 9 Dec 2021 01:29:45 +0900 Subject: [PATCH 029/146] A bit of cleanup + xmldocs on classes/members --- ...TestScenePlaylistsRoomSettingsPlaylist.cs} | 4 +- .../Components/MatchBeatmapDetailArea.cs | 2 +- .../OnlinePlay/DrawableRoomPlaylist.cs | 27 +++++++++- .../OnlinePlay/DrawableRoomPlaylistItem.cs | 50 +++++++++++++------ .../Playlists/PlaylistsRoomSettingsOverlay.cs | 2 +- ...st.cs => PlaylistsRoomSettingsPlaylist.cs} | 7 ++- 6 files changed, 71 insertions(+), 21 deletions(-) rename osu.Game.Tests/Visual/Multiplayer/{TestScenePlaylistsRoomPlaylist.cs => TestScenePlaylistsRoomSettingsPlaylist.cs} (97%) rename osu.Game/Screens/OnlinePlay/Playlists/{PlaylistsRoomPlaylist.cs => PlaylistsRoomSettingsPlaylist.cs} (69%) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsRoomPlaylist.cs b/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsRoomSettingsPlaylist.cs similarity index 97% rename from osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsRoomPlaylist.cs rename to osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsRoomSettingsPlaylist.cs index 264f6aa2c5..93ccd5f1e1 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsRoomPlaylist.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsRoomSettingsPlaylist.cs @@ -24,7 +24,7 @@ using osuTK.Input; namespace osu.Game.Tests.Visual.Multiplayer { - public class TestScenePlaylistsRoomPlaylist : OsuManualInputManagerTestScene + public class TestScenePlaylistsRoomSettingsPlaylist : OsuManualInputManagerTestScene { private TestPlaylist playlist; @@ -175,7 +175,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddUntilStep("wait for items to load", () => playlist.ItemMap.Values.All(i => i.IsLoaded)); } - private class TestPlaylist : PlaylistsRoomPlaylist + private class TestPlaylist : PlaylistsRoomSettingsPlaylist { public new IReadOnlyDictionary> ItemMap => base.ItemMap; diff --git a/osu.Game/Screens/OnlinePlay/Components/MatchBeatmapDetailArea.cs b/osu.Game/Screens/OnlinePlay/Components/MatchBeatmapDetailArea.cs index d56acff8c7..c29b8db26c 100644 --- a/osu.Game/Screens/OnlinePlay/Components/MatchBeatmapDetailArea.cs +++ b/osu.Game/Screens/OnlinePlay/Components/MatchBeatmapDetailArea.cs @@ -44,7 +44,7 @@ namespace osu.Game.Screens.OnlinePlay.Components { RelativeSizeAxes = Axes.Both, Padding = new MarginPadding { Bottom = 10 }, - Child = playlist = new PlaylistsRoomPlaylist + Child = playlist = new PlaylistsRoomSettingsPlaylist { RelativeSizeAxes = Axes.Both, AllowSelection = true, diff --git a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylist.cs b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylist.cs index e76d905849..5e58514705 100644 --- a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylist.cs +++ b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylist.cs @@ -12,8 +12,15 @@ using osuTK; namespace osu.Game.Screens.OnlinePlay { + /// + /// A list scrollable list which displays the s in a . + /// public class DrawableRoomPlaylist : OsuRearrangeableListContainer { + /// + /// The currently-selected item, used to show a border around items. + /// May be updated by playlist items if is true. + /// public readonly Bindable SelectedItem = new Bindable(); /// @@ -22,12 +29,15 @@ namespace osu.Game.Screens.OnlinePlay public Action DeletionRequested; /// - /// Invoked to request showing the results for an item. + /// Invoked when an item requests its results to be shown. /// public Action ShowResultsRequested; private bool allowReordering; + /// + /// Whether to allow reordering items in the playlist. + /// public bool AllowReordering { get => allowReordering; @@ -42,6 +52,10 @@ namespace osu.Game.Screens.OnlinePlay private bool allowDeletion; + /// + /// Whether to allow deleting items from the playlist. + /// If true, requests to delete items may be satisfied via . + /// public bool AllowDeletion { get => allowDeletion; @@ -56,6 +70,10 @@ namespace osu.Game.Screens.OnlinePlay private bool allowSelection; + /// + /// Whether to allow selecting items from the playlist. + /// If true, clicking on items in the playlist will change the value of . + /// public bool AllowSelection { get => allowSelection; @@ -70,6 +88,10 @@ namespace osu.Game.Screens.OnlinePlay private bool allowShowingResults; + /// + /// Whether to allow items to request their results to be shown. + /// If true, requests to show the results may be satisfied via . + /// public bool AllowShowingResults { get => allowShowingResults; @@ -84,6 +106,9 @@ namespace osu.Game.Screens.OnlinePlay private bool showItemOwners; + /// + /// Whether to show the avatar of users which own each playlist item. + /// public bool ShowItemOwners { get => showItemOwners; diff --git a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs index 28b4997b63..fca944e9b6 100644 --- a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs +++ b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs @@ -40,11 +40,30 @@ namespace osu.Game.Screens.OnlinePlay public const float HEIGHT = 50; public const float ICON_HEIGHT = 34; + /// + /// Invoked when this item requests to be deleted. + /// public Action RequestDeletion; + + /// + /// Invoked when this item requests its results to be shown. + /// public Action ShowResultsRequested; + /// + /// The currently-selected item, used to show a border around this item. + /// May be updated by this item if is true. + /// public readonly Bindable SelectedItem = new Bindable(); + public readonly PlaylistItem Item; + + private readonly DelayedLoadWrapper onScreenLoader = new DelayedLoadWrapper(Empty) { RelativeSizeAxes = Axes.Both }; + private readonly IBindable valid = new Bindable(); + private readonly Bindable beatmap = new Bindable(); + private readonly Bindable ruleset = new Bindable(); + private readonly BindableList requiredMods = new BindableList(); + private Container maskingContainer; private Container difficultyIconContainer; private LinkFlowContainer beatmapText; @@ -55,14 +74,8 @@ namespace osu.Game.Screens.OnlinePlay private UpdateableAvatar ownerAvatar; private Drawable removeButton; private Drawable showResultsButton; - - private readonly IBindable valid = new Bindable(); - - private readonly Bindable beatmap = new Bindable(); - private readonly Bindable ruleset = new Bindable(); - private readonly BindableList requiredMods = new BindableList(); - - public readonly PlaylistItem Item; + private PanelBackground panelBackground; + private FillFlowContainer mainFillFlow; [Resolved] private OsuColour colours { get; set; } @@ -73,12 +86,6 @@ namespace osu.Game.Screens.OnlinePlay [Resolved] private BeatmapLookupCache beatmapLookupCache { get; set; } - private PanelBackground panelBackground; - - private readonly DelayedLoadWrapper onScreenLoader = new DelayedLoadWrapper(Empty) { RelativeSizeAxes = Axes.Both }; - - private FillFlowContainer mainFillFlow; - protected override bool ShouldBeConsideredForInput(Drawable child) => AllowReordering || AllowDeletion || !AllowSelection || SelectedItem.Value == Model; public DrawableRoomPlaylistItem(PlaylistItem item) @@ -157,8 +164,14 @@ namespace osu.Game.Screens.OnlinePlay refresh(); } + /// + /// Whether this item can be selected. + /// public bool AllowSelection { get; set; } + /// + /// Whether this item can be reordered in the playlist. + /// public bool AllowReordering { get => ShowDragHandle.Value; @@ -167,6 +180,9 @@ namespace osu.Game.Screens.OnlinePlay private bool allowDeletion; + /// + /// Whether this item can be deleted. + /// public bool AllowDeletion { get => allowDeletion; @@ -181,6 +197,9 @@ namespace osu.Game.Screens.OnlinePlay private bool allowShowingResults; + /// + /// Whether this item can have results shown. + /// public bool AllowShowingResults { get => allowShowingResults; @@ -195,6 +214,9 @@ namespace osu.Game.Screens.OnlinePlay private bool showItemOwner; + /// + /// Whether to display the avatar of the user which owns this playlist item. + /// public bool ShowItemOwner { get => showItemOwner; diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSettingsOverlay.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSettingsOverlay.cs index 915ec356d5..8f31422add 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSettingsOverlay.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSettingsOverlay.cs @@ -205,7 +205,7 @@ namespace osu.Game.Screens.OnlinePlay.Playlists { new Drawable[] { - playlist = new PlaylistsRoomPlaylist + playlist = new PlaylistsRoomSettingsPlaylist { RelativeSizeAxes = Axes.Both, } diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomPlaylist.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSettingsPlaylist.cs similarity index 69% rename from osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomPlaylist.cs rename to osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSettingsPlaylist.cs index d8c316b642..bbe67e76e3 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomPlaylist.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSettingsPlaylist.cs @@ -6,9 +6,12 @@ using osu.Framework.Extensions.IEnumerableExtensions; namespace osu.Game.Screens.OnlinePlay.Playlists { - public class PlaylistsRoomPlaylist : DrawableRoomPlaylist + /// + /// A which is displayed during the setup stage of a playlists room. + /// + public class PlaylistsRoomSettingsPlaylist : DrawableRoomPlaylist { - public PlaylistsRoomPlaylist() + public PlaylistsRoomSettingsPlaylist() { AllowReordering = true; AllowDeletion = true; From 273042aa16882993f553e9eb43fa44d2bdf25faa Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 9 Dec 2021 01:47:46 +0900 Subject: [PATCH 030/146] Add virtual method for creating different DrawablePlaylistItem types --- .../OnlinePlay/DrawableRoomPlaylist.cs | 22 ++++++++++--------- .../Match/Playlist/MultiplayerHistoryList.cs | 9 ++++---- .../Match/Playlist/MultiplayerQueueList.cs | 9 ++++---- 3 files changed, 22 insertions(+), 18 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylist.cs b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylist.cs index 5e58514705..1abc7c47d6 100644 --- a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylist.cs +++ b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylist.cs @@ -131,16 +131,18 @@ namespace osu.Game.Screens.OnlinePlay Spacing = new Vector2(0, 2) }; - protected override OsuRearrangeableListItem CreateOsuDrawable(PlaylistItem item) => new DrawableRoomPlaylistItem(item) + protected sealed override OsuRearrangeableListItem CreateOsuDrawable(PlaylistItem item) => CreateDrawablePlaylistItem(item).With(d => { - SelectedItem = { BindTarget = SelectedItem }, - RequestDeletion = i => DeletionRequested?.Invoke(i), - AllowReordering = AllowReordering, - AllowDeletion = AllowDeletion, - AllowSelection = AllowSelection, - AllowShowingResults = AllowShowingResults, - ShowItemOwner = ShowItemOwners, - ShowResultsRequested = i => ShowResultsRequested?.Invoke(i) - }; + d.SelectedItem.BindTarget = SelectedItem; + d.RequestDeletion = i => DeletionRequested?.Invoke(i); + d.AllowReordering = AllowReordering; + d.AllowDeletion = AllowDeletion; + d.AllowSelection = AllowSelection; + d.AllowShowingResults = AllowShowingResults; + d.ShowItemOwner = ShowItemOwners; + d.ShowResultsRequested = i => ShowResultsRequested?.Invoke(i); + }); + + protected virtual DrawableRoomPlaylistItem CreateDrawablePlaylistItem(PlaylistItem item) => new DrawableRoomPlaylistItem(item); } } diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerHistoryList.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerHistoryList.cs index 7102738271..32d355d149 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerHistoryList.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerHistoryList.cs @@ -5,7 +5,6 @@ using System.Collections.Generic; using System.Linq; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Game.Graphics.Containers; using osu.Game.Online.Rooms; using osuTK; @@ -16,14 +15,16 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match.Playlist /// public class MultiplayerHistoryList : DrawableRoomPlaylist { + public MultiplayerHistoryList() + { + ShowItemOwners = true; + } + protected override FillFlowContainer> CreateListFillFlowContainer() => new HistoryFillFlowContainer { Spacing = new Vector2(0, 2) }; - protected override OsuRearrangeableListItem CreateOsuDrawable(PlaylistItem item) - => base.CreateOsuDrawable(item).With(d => ((DrawableRoomPlaylistItem)d).ShowItemOwner = true); - private class HistoryFillFlowContainer : FillFlowContainer> { public override IEnumerable FlowingChildren => base.FlowingChildren.OfType>().OrderByDescending(item => item.Model.PlayedAt); diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerQueueList.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerQueueList.cs index 814ea48646..7dfee36895 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerQueueList.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerQueueList.cs @@ -7,7 +7,6 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Game.Graphics.Containers; using osu.Game.Online.Rooms; using osuTK; @@ -18,14 +17,16 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match.Playlist /// public class MultiplayerQueueList : DrawableRoomPlaylist { + public MultiplayerQueueList() + { + ShowItemOwners = true; + } + protected override FillFlowContainer> CreateListFillFlowContainer() => new QueueFillFlowContainer { Spacing = new Vector2(0, 2) }; - protected override OsuRearrangeableListItem CreateOsuDrawable(PlaylistItem item) - => base.CreateOsuDrawable(item).With(d => ((DrawableRoomPlaylistItem)d).ShowItemOwner = true); - private class QueueFillFlowContainer : FillFlowContainer> { [Resolved(typeof(Room), nameof(Room.Playlist))] From 23332995d133672ec63e3c29e8859d58c5d8625a Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 9 Dec 2021 01:52:59 +0900 Subject: [PATCH 031/146] Invert naming of exposed actions --- osu.Game/Screens/OnlinePlay/DrawableRoomPlaylist.cs | 12 ++++++------ .../Screens/OnlinePlay/DrawableRoomPlaylistItem.cs | 4 ++-- .../Playlists/PlaylistsRoomSettingsPlaylist.cs | 2 +- .../OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs | 2 +- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylist.cs b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylist.cs index 1abc7c47d6..8bd2daa2c3 100644 --- a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylist.cs +++ b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylist.cs @@ -26,12 +26,12 @@ namespace osu.Game.Screens.OnlinePlay /// /// Invoked when an item is requested to be deleted. /// - public Action DeletionRequested; + public Action RequestDeletion; /// /// Invoked when an item requests its results to be shown. /// - public Action ShowResultsRequested; + public Action RequestResults; private bool allowReordering; @@ -54,7 +54,7 @@ namespace osu.Game.Screens.OnlinePlay /// /// Whether to allow deleting items from the playlist. - /// If true, requests to delete items may be satisfied via . + /// If true, requests to delete items may be satisfied via . /// public bool AllowDeletion { @@ -90,7 +90,7 @@ namespace osu.Game.Screens.OnlinePlay /// /// Whether to allow items to request their results to be shown. - /// If true, requests to show the results may be satisfied via . + /// If true, requests to show the results may be satisfied via . /// public bool AllowShowingResults { @@ -134,13 +134,13 @@ namespace osu.Game.Screens.OnlinePlay protected sealed override OsuRearrangeableListItem CreateOsuDrawable(PlaylistItem item) => CreateDrawablePlaylistItem(item).With(d => { d.SelectedItem.BindTarget = SelectedItem; - d.RequestDeletion = i => DeletionRequested?.Invoke(i); + d.RequestDeletion = i => RequestDeletion?.Invoke(i); d.AllowReordering = AllowReordering; d.AllowDeletion = AllowDeletion; d.AllowSelection = AllowSelection; d.AllowShowingResults = AllowShowingResults; d.ShowItemOwner = ShowItemOwners; - d.ShowResultsRequested = i => ShowResultsRequested?.Invoke(i); + d.RequestResults = i => RequestResults?.Invoke(i); }); protected virtual DrawableRoomPlaylistItem CreateDrawablePlaylistItem(PlaylistItem item) => new DrawableRoomPlaylistItem(item); diff --git a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs index fca944e9b6..7a1c069df3 100644 --- a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs +++ b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs @@ -48,7 +48,7 @@ namespace osu.Game.Screens.OnlinePlay /// /// Invoked when this item requests its results to be shown. /// - public Action ShowResultsRequested; + public Action RequestResults; /// /// The currently-selected item, used to show a border around this item. @@ -410,7 +410,7 @@ namespace osu.Game.Screens.OnlinePlay { showResultsButton = new ShowResultsButton { - Action = () => ShowResultsRequested?.Invoke(Item), + Action = () => RequestResults?.Invoke(Item), Alpha = AllowShowingResults ? 1 : 0, }, Item.Beatmap.Value == null ? Empty() : new PlaylistDownloadButton(Item), diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSettingsPlaylist.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSettingsPlaylist.cs index bbe67e76e3..2fe215eef2 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSettingsPlaylist.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSettingsPlaylist.cs @@ -16,7 +16,7 @@ namespace osu.Game.Screens.OnlinePlay.Playlists AllowReordering = true; AllowDeletion = true; - DeletionRequested = item => + RequestDeletion = item => { var nextItem = Items.GetNext(item); diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs index dd2d22b742..4114a5e9a0 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs @@ -95,7 +95,7 @@ namespace osu.Game.Screens.OnlinePlay.Playlists SelectedItem = { BindTarget = SelectedItem }, AllowSelection = true, AllowShowingResults = true, - ShowResultsRequested = item => + RequestResults = item => { Debug.Assert(RoomId.Value != null); ParentScreen?.Push(new PlaylistsResultsScreen(null, RoomId.Value.Value, item, false)); From ce081c4acc6e02f8d22fac4d100050c1d40f0cac Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 9 Dec 2021 02:01:11 +0900 Subject: [PATCH 032/146] Fix missing propagation of OwnerId in tests --- osu.Game/Online/Rooms/MultiplayerPlaylistItem.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Online/Rooms/MultiplayerPlaylistItem.cs b/osu.Game/Online/Rooms/MultiplayerPlaylistItem.cs index cee6d8fe41..8ec073ff1e 100644 --- a/osu.Game/Online/Rooms/MultiplayerPlaylistItem.cs +++ b/osu.Game/Online/Rooms/MultiplayerPlaylistItem.cs @@ -62,6 +62,7 @@ namespace osu.Game.Online.Rooms public MultiplayerPlaylistItem(PlaylistItem item) { ID = item.ID; + OwnerID = item.OwnerID; BeatmapID = item.BeatmapID; BeatmapChecksum = item.Beatmap.Value?.MD5Hash ?? string.Empty; RulesetID = item.RulesetID; From c34c580ad4a7da49bfe3cc7a5f6bc44fc9e750fb Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 9 Dec 2021 02:12:33 +0900 Subject: [PATCH 033/146] Add client-side + interface implementation --- .../Online/TestSceneMultiplayerQueueList.cs | 177 ++++++++++++++++++ .../Multiplayer/IMultiplayerRoomServer.cs | 6 + .../Online/Multiplayer/MultiplayerClient.cs | 2 + .../Multiplayer/OnlineMultiplayerClient.cs | 8 + .../Match/Playlist/MultiplayerQueueList.cs | 43 +++++ .../Multiplayer/TestMultiplayerClient.cs | 30 +++ 6 files changed, 266 insertions(+) create mode 100644 osu.Game.Tests/Visual/Online/TestSceneMultiplayerQueueList.cs diff --git a/osu.Game.Tests/Visual/Online/TestSceneMultiplayerQueueList.cs b/osu.Game.Tests/Visual/Online/TestSceneMultiplayerQueueList.cs new file mode 100644 index 0000000000..d693ee60f0 --- /dev/null +++ b/osu.Game.Tests/Visual/Online/TestSceneMultiplayerQueueList.cs @@ -0,0 +1,177 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Linq; +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Audio; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Platform; +using osu.Framework.Testing; +using osu.Game.Beatmaps; +using osu.Game.Database; +using osu.Game.Graphics.Containers; +using osu.Game.Online.Multiplayer; +using osu.Game.Online.Rooms; +using osu.Game.Rulesets; +using osu.Game.Screens.OnlinePlay; +using osu.Game.Screens.OnlinePlay.Multiplayer.Match.Playlist; +using osu.Game.Tests.Resources; +using osu.Game.Tests.Visual.Multiplayer; +using osuTK; +using osuTK.Input; + +namespace osu.Game.Tests.Visual.Online +{ + public class TestSceneMultiplayerQueueList : MultiplayerTestScene + { + private MultiplayerQueueList playlist; + + [Cached(typeof(UserLookupCache))] + private readonly TestUserLookupCache userLookupCache = new TestUserLookupCache(); + + private BeatmapManager beatmaps; + private RulesetStore rulesets; + private BeatmapSetInfo importedSet; + private BeatmapInfo importedBeatmap; + + [BackgroundDependencyLoader] + private void load(GameHost host, AudioManager audio) + { + Dependencies.Cache(rulesets = new RulesetStore(ContextFactory)); + Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, audio, Resources, host, Beatmap.Default)); + } + + public override void SetUpSteps() + { + base.SetUpSteps(); + + AddStep("create playlist", () => + { + Child = playlist = new MultiplayerQueueList + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(500, 300), + Items = { BindTarget = Client.APIRoom!.Playlist } + }; + }); + + AddStep("import beatmap", () => + { + beatmaps.Import(TestResources.GetQuickTestBeatmapForImport()).Wait(); + importedSet = beatmaps.GetAllUsableBeatmapSetsEnumerable(IncludedDetails.All).First(); + importedBeatmap = importedSet.Beatmaps.First(b => b.RulesetID == 0); + }); + + AddStep("change to all players mode", () => Client.ChangeSettings(new MultiplayerRoomSettings { QueueMode = QueueMode.AllPlayers })); + } + + [Test] + public void TestDeleteButtonHiddenWithSingleItem() + { + AddStep("set all players queue mode", () => Client.ChangeSettings(new MultiplayerRoomSettings { QueueMode = QueueMode.AllPlayers })); + AddUntilStep("wait for queue mode change", () => Client.APIRoom?.QueueMode.Value == QueueMode.AllPlayers); + + assertDeleteButtonVisibility(0, false); + + addPlaylistItem(() => API.LocalUser.Value.OnlineID); + assertDeleteButtonVisibility(0, true); + + deleteItem(1); + assertDeleteButtonVisibility(0, false); + } + + [Test] + public void TestDeleteButtonHiddenInHostOnlyMode() + { + AddStep("set all players queue mode", () => Client.ChangeSettings(new MultiplayerRoomSettings { QueueMode = QueueMode.AllPlayers })); + AddUntilStep("wait for queue mode change", () => Client.APIRoom?.QueueMode.Value == QueueMode.AllPlayers); + addPlaylistItem(() => 1234); + + AddStep("set host-only queue mode", () => Client.ChangeSettings(new MultiplayerRoomSettings { QueueMode = QueueMode.HostOnly })); + AddUntilStep("wait for queue mode change", () => Client.APIRoom?.QueueMode.Value == QueueMode.HostOnly); + + assertDeleteButtonVisibility(0, false); + } + + [Test] + public void TestOnlyItemOwnerHasDeleteButton() + { + AddStep("set all players queue mode", () => Client.ChangeSettings(new MultiplayerRoomSettings { QueueMode = QueueMode.AllPlayers })); + AddUntilStep("wait for queue mode change", () => Client.APIRoom?.QueueMode.Value == QueueMode.AllPlayers); + + addPlaylistItem(() => API.LocalUser.Value.OnlineID); + assertDeleteButtonVisibility(0, true); + assertDeleteButtonVisibility(1, true); + + addPlaylistItem(() => 1234); + assertDeleteButtonVisibility(2, false); + } + + [Test] + public void TestNonOwnerDoesNotHaveDeleteButton() + { + AddStep("set all players queue mode", () => Client.ChangeSettings(new MultiplayerRoomSettings { QueueMode = QueueMode.AllPlayers })); + AddUntilStep("wait for queue mode change", () => Client.APIRoom?.QueueMode.Value == QueueMode.AllPlayers); + + addPlaylistItem(() => 1234); + assertDeleteButtonVisibility(0, true); + assertDeleteButtonVisibility(1, false); + } + + [Test] + public void TestSelectedItemDoesNotHaveDeleteButton() + { + AddStep("set all players queue mode", () => Client.ChangeSettings(new MultiplayerRoomSettings { QueueMode = QueueMode.AllPlayers })); + AddUntilStep("wait for queue mode change", () => Client.APIRoom?.QueueMode.Value == QueueMode.AllPlayers); + + addPlaylistItem(() => API.LocalUser.Value.OnlineID); + assertDeleteButtonVisibility(0, true); + + AddStep("set first playlist item as selected", () => playlist.SelectedItem.Value = playlist.Items[0]); + assertDeleteButtonVisibility(0, false); + } + + private void addPlaylistItem(Func userId) + { + long itemId = -1; + + AddStep("add playlist item", () => + { + MultiplayerPlaylistItem item = new MultiplayerPlaylistItem(new PlaylistItem + { + Beatmap = { Value = importedBeatmap }, + BeatmapID = importedBeatmap.OnlineID ?? -1, + }); + + Client.AddUserPlaylistItem(userId(), item); + + itemId = item.ID; + }); + + AddUntilStep("item arrived in playlist", () => playlist.ChildrenOfType>().Any(i => i.Model.ID == itemId)); + } + + private void deleteItem(int index) + { + OsuRearrangeableListItem item = null; + + AddStep($"move mouse to delete button {index}", () => + { + item = playlist.ChildrenOfType>().ElementAt(index); + InputManager.MoveMouseTo(item.ChildrenOfType().ElementAt(0)); + }); + + AddStep("click", () => InputManager.Click(MouseButton.Left)); + + AddUntilStep("item removed from playlist", () => !playlist.ChildrenOfType>().Contains(item)); + } + + private void assertDeleteButtonVisibility(int index, bool visible) + => AddUntilStep($"delete button {index} {(visible ? "is" : "is not")} visible", + () => (playlist.ChildrenOfType().ElementAt(index).Alpha > 0) == visible); + } +} diff --git a/osu.Game/Online/Multiplayer/IMultiplayerRoomServer.cs b/osu.Game/Online/Multiplayer/IMultiplayerRoomServer.cs index 3e84e4b904..65467e6ba9 100644 --- a/osu.Game/Online/Multiplayer/IMultiplayerRoomServer.cs +++ b/osu.Game/Online/Multiplayer/IMultiplayerRoomServer.cs @@ -82,5 +82,11 @@ namespace osu.Game.Online.Multiplayer /// /// The item to add. Task AddPlaylistItem(MultiplayerPlaylistItem item); + + /// + /// Removes an item from the playlist. + /// + /// The item to remove. + Task RemovePlaylistItem(long playlistItemId); } } diff --git a/osu.Game/Online/Multiplayer/MultiplayerClient.cs b/osu.Game/Online/Multiplayer/MultiplayerClient.cs index 7e874495c8..34dc7ea5ea 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerClient.cs @@ -335,6 +335,8 @@ namespace osu.Game.Online.Multiplayer public abstract Task AddPlaylistItem(MultiplayerPlaylistItem item); + public abstract Task RemovePlaylistItem(long playlistItemId); + Task IMultiplayerClient.RoomStateChanged(MultiplayerRoomState state) { if (Room == null) diff --git a/osu.Game/Online/Multiplayer/OnlineMultiplayerClient.cs b/osu.Game/Online/Multiplayer/OnlineMultiplayerClient.cs index 41687b54b0..7314603603 100644 --- a/osu.Game/Online/Multiplayer/OnlineMultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/OnlineMultiplayerClient.cs @@ -162,6 +162,14 @@ namespace osu.Game.Online.Multiplayer return connection.InvokeAsync(nameof(IMultiplayerServer.AddPlaylistItem), item); } + public override Task RemovePlaylistItem(long playlistItemId) + { + if (!IsConnected.Value) + return Task.CompletedTask; + + return connection.InvokeAsync(nameof(IMultiplayerServer.RemovePlaylistItem), playlistItemId); + } + protected override Task GetAPIBeatmap(int beatmapId, CancellationToken cancellationToken = default) { return beatmapLookupCache.GetBeatmapAsync(beatmapId, cancellationToken); diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerQueueList.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerQueueList.cs index 7dfee36895..e74b2e8384 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerQueueList.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerQueueList.cs @@ -7,6 +7,8 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Game.Online.API; +using osu.Game.Online.Multiplayer; using osu.Game.Online.Rooms; using osuTK; @@ -27,6 +29,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match.Playlist Spacing = new Vector2(0, 2) }; + protected override DrawableRoomPlaylistItem CreateDrawablePlaylistItem(PlaylistItem item) => new QueuePlaylistItem(item); + private class QueueFillFlowContainer : FillFlowContainer> { [Resolved(typeof(Room), nameof(Room.Playlist))] @@ -40,5 +44,44 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match.Playlist public override IEnumerable FlowingChildren => base.FlowingChildren.OfType>().OrderBy(item => item.Model.PlaylistOrder); } + + private class QueuePlaylistItem : DrawableRoomPlaylistItem + { + [Resolved] + private IAPIProvider api { get; set; } + + [Resolved] + private MultiplayerClient multiplayerClient { get; set; } + + [Resolved(typeof(Room), nameof(Room.QueueMode))] + private Bindable queueMode { get; set; } + + [Resolved(typeof(Room), nameof(Room.Playlist))] + private BindableList playlist { get; set; } + + public QueuePlaylistItem(PlaylistItem item) + : base(item) + { + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + RequestDeletion = item => multiplayerClient.RemovePlaylistItem(item.ID); + + playlist.BindCollectionChanged((_, __) => updateDeleteButtonVisibility()); + queueMode.BindValueChanged(_ => updateDeleteButtonVisibility()); + SelectedItem.BindValueChanged(_ => updateDeleteButtonVisibility(), true); + } + + private void updateDeleteButtonVisibility() + { + AllowDeletion = queueMode.Value != QueueMode.HostOnly + && playlist.Count > 1 + && Item.OwnerID == api.LocalUser.Value.OnlineID + && SelectedItem.Value != Item; + } + } } } diff --git a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs index b3ea5bdc4a..1f4e031b40 100644 --- a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs +++ b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs @@ -339,6 +339,36 @@ namespace osu.Game.Tests.Visual.Multiplayer public override Task AddPlaylistItem(MultiplayerPlaylistItem item) => AddUserPlaylistItem(api.LocalUser.Value.OnlineID, item); + public async Task RemoveUserPlaylistItem(int userId, long playlistItemId) + { + Debug.Assert(Room != null); + Debug.Assert(APIRoom != null); + + if (Room.Settings.QueueMode == QueueMode.HostOnly) + throw new InvalidOperationException("Items cannot be removed in host-only mode."); + + if (Room.Playlist.Count == 1) + throw new InvalidOperationException("The singular item in the room cannot be removed."); + + var item = serverSidePlaylist.Find(i => i.ID == playlistItemId); + + if (item == null) + throw new InvalidOperationException("Item does not exist in the room."); + + if (item == currentItem) + throw new InvalidOperationException("The room's current item cannot be removed."); + + if (item.OwnerID != userId) + throw new InvalidOperationException("Attempted to remove an item which is not owned by the user."); + + serverSidePlaylist.Remove(item); + await ((IMultiplayerClient)this).PlaylistItemRemoved(playlistItemId).ConfigureAwait(false); + + await updateCurrentItem(Room).ConfigureAwait(false); + } + + public override Task RemovePlaylistItem(long playlistItemId) => RemoveUserPlaylistItem(api.LocalUser.Value.OnlineID, playlistItemId); + protected override Task GetAPIBeatmap(int beatmapId, CancellationToken cancellationToken = default) { IBeatmapSetInfo? set = roomManager.ServerSideRooms.SelectMany(r => r.Playlist) From 8398f86440ae0f941ea07e9dc51366d6e135db16 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 9 Dec 2021 04:02:16 +0900 Subject: [PATCH 034/146] Don't consider expired items in visibility check --- .../Match/Playlist/MultiplayerQueueList.cs | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerQueueList.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerQueueList.cs index e74b2e8384..72bcf7d343 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerQueueList.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerQueueList.cs @@ -29,7 +29,10 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match.Playlist Spacing = new Vector2(0, 2) }; - protected override DrawableRoomPlaylistItem CreateDrawablePlaylistItem(PlaylistItem item) => new QueuePlaylistItem(item); + protected override DrawableRoomPlaylistItem CreateDrawablePlaylistItem(PlaylistItem item) => new QueuePlaylistItem(item) + { + Items = { BindTarget = Items } + }; private class QueueFillFlowContainer : FillFlowContainer> { @@ -47,6 +50,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match.Playlist private class QueuePlaylistItem : DrawableRoomPlaylistItem { + public readonly IBindableList Items = new BindableList(); + [Resolved] private IAPIProvider api { get; set; } @@ -56,9 +61,6 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match.Playlist [Resolved(typeof(Room), nameof(Room.QueueMode))] private Bindable queueMode { get; set; } - [Resolved(typeof(Room), nameof(Room.Playlist))] - private BindableList playlist { get; set; } - public QueuePlaylistItem(PlaylistItem item) : base(item) { @@ -70,7 +72,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match.Playlist RequestDeletion = item => multiplayerClient.RemovePlaylistItem(item.ID); - playlist.BindCollectionChanged((_, __) => updateDeleteButtonVisibility()); + Items.BindCollectionChanged((_, __) => updateDeleteButtonVisibility()); queueMode.BindValueChanged(_ => updateDeleteButtonVisibility()); SelectedItem.BindValueChanged(_ => updateDeleteButtonVisibility(), true); } @@ -78,7 +80,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match.Playlist private void updateDeleteButtonVisibility() { AllowDeletion = queueMode.Value != QueueMode.HostOnly - && playlist.Count > 1 + && Items.Count > 1 && Item.OwnerID == api.LocalUser.Value.OnlineID && SelectedItem.Value != Item; } From 4df2047a581d70687e5854209285ebdfadf2ca3f Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 9 Dec 2021 04:12:24 +0900 Subject: [PATCH 035/146] Prevent removal of expired items in TestMultiplayerClient --- osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs index 1f4e031b40..c82b748a2b 100644 --- a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs +++ b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs @@ -361,6 +361,9 @@ namespace osu.Game.Tests.Visual.Multiplayer if (item.OwnerID != userId) throw new InvalidOperationException("Attempted to remove an item which is not owned by the user."); + if (item.Expired) + throw new InvalidOperationException("Attempted to remove an item which has already been played"); + serverSidePlaylist.Remove(item); await ((IMultiplayerClient)this).PlaylistItemRemoved(playlistItemId).ConfigureAwait(false); From 80b2768a5f6c2d18cdd6225becc9a55cf8172210 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 9 Dec 2021 04:18:53 +0900 Subject: [PATCH 036/146] Mirror recent server-side changes --- .../Tests/Visual/Multiplayer/TestMultiplayerClient.cs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs index c82b748a2b..f151add430 100644 --- a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs +++ b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs @@ -347,9 +347,6 @@ namespace osu.Game.Tests.Visual.Multiplayer if (Room.Settings.QueueMode == QueueMode.HostOnly) throw new InvalidOperationException("Items cannot be removed in host-only mode."); - if (Room.Playlist.Count == 1) - throw new InvalidOperationException("The singular item in the room cannot be removed."); - var item = serverSidePlaylist.Find(i => i.ID == playlistItemId); if (item == null) @@ -362,7 +359,7 @@ namespace osu.Game.Tests.Visual.Multiplayer throw new InvalidOperationException("Attempted to remove an item which is not owned by the user."); if (item.Expired) - throw new InvalidOperationException("Attempted to remove an item which has already been played"); + throw new InvalidOperationException("Attempted to remove an item which has already been played."); serverSidePlaylist.Remove(item); await ((IMultiplayerClient)this).PlaylistItemRemoved(playlistItemId).ConfigureAwait(false); @@ -471,11 +468,12 @@ namespace osu.Game.Tests.Visual.Multiplayer await updatePlaylistOrder(Room).ConfigureAwait(false); } + private IEnumerable upcomingItems => serverSidePlaylist.Where(i => !i.Expired).OrderBy(i => i.PlaylistOrder); + private async Task updateCurrentItem(MultiplayerRoom room, bool notify = true) { // Pick the next non-expired playlist item by playlist order, or default to the most-recently-expired item. - MultiplayerPlaylistItem nextItem = serverSidePlaylist.Where(i => !i.Expired).OrderBy(i => i.PlaylistOrder).FirstOrDefault() - ?? serverSidePlaylist.OrderByDescending(i => i.PlayedAt).First(); + MultiplayerPlaylistItem nextItem = upcomingItems.FirstOrDefault() ?? serverSidePlaylist.OrderByDescending(i => i.PlayedAt).First(); currentIndex = serverSidePlaylist.IndexOf(nextItem); From aec36adf6ce2fff49265a6ca3cc6bfb50714b518 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 9 Dec 2021 04:20:23 +0900 Subject: [PATCH 037/146] Fix test failures --- .../Multiplayer/TestSceneDrawableRoomPlaylist.cs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs index 269cd15a0f..c9a5552f39 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs @@ -136,7 +136,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("import beatmap", () => manager.Import(beatmap.BeatmapSet).Wait()); - createPlaylist(beatmap); + createPlaylistWithBeatmaps(beatmap); assertDownloadButtonVisible(false); @@ -159,7 +159,7 @@ namespace osu.Game.Tests.Visual.Multiplayer var byChecksum = CreateAPIBeatmap(); byChecksum.Checksum = "1337"; // Some random checksum that does not exist locally. - createPlaylist(byOnlineId, byChecksum); + createPlaylistWithBeatmaps(byOnlineId, byChecksum); AddAssert("download buttons shown", () => playlist.ChildrenOfType().All(d => d.IsPresent)); } @@ -173,7 +173,7 @@ namespace osu.Game.Tests.Visual.Multiplayer beatmap.BeatmapSet.HasExplicitContent = true; - createPlaylist(beatmap); + createPlaylistWithBeatmaps(beatmap); } [Test] @@ -246,7 +246,7 @@ namespace osu.Game.Tests.Visual.Multiplayer => AddAssert($"delete button {index} {(visible ? "is" : "is not")} visible", () => (playlist.ChildrenOfType().ElementAt(2 + index * 2).Alpha > 0) == visible); - private void createPlaylist(Action setupPlaylist) + private void createPlaylist(Action setupPlaylist = null) { AddStep("create playlist", () => { @@ -257,6 +257,8 @@ namespace osu.Game.Tests.Visual.Multiplayer Size = new Vector2(500, 300) }; + setupPlaylist?.Invoke(playlist); + for (int i = 0; i < 20; i++) { playlist.Items.Add(new PlaylistItem @@ -292,7 +294,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddUntilStep("wait for items to load", () => playlist.ItemMap.Values.All(i => i.IsLoaded)); } - private void createPlaylist(params IBeatmapInfo[] beatmaps) + private void createPlaylistWithBeatmaps(params IBeatmapInfo[] beatmaps) { AddStep("create playlist", () => { From 1a0945dabadbe14990004cd8dfbbb02a8d8271eb Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 9 Dec 2021 14:11:29 +0900 Subject: [PATCH 038/146] Siplify condition, allow host to always remove items --- .../Online/TestSceneMultiplayerQueueList.cs | 80 +++++++++---------- .../Match/Playlist/MultiplayerQueueList.cs | 17 ++-- 2 files changed, 43 insertions(+), 54 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneMultiplayerQueueList.cs b/osu.Game.Tests/Visual/Online/TestSceneMultiplayerQueueList.cs index d693ee60f0..19e5624a04 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneMultiplayerQueueList.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneMultiplayerQueueList.cs @@ -13,6 +13,7 @@ using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Database; using osu.Game.Graphics.Containers; +using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.Multiplayer; using osu.Game.Online.Rooms; using osu.Game.Rulesets; @@ -55,6 +56,7 @@ namespace osu.Game.Tests.Visual.Online Anchor = Anchor.Centre, Origin = Anchor.Centre, Size = new Vector2(500, 300), + SelectedItem = { BindTarget = Client.CurrentMatchPlayingItem }, Items = { BindTarget = Client.APIRoom!.Playlist } }; }); @@ -70,69 +72,59 @@ namespace osu.Game.Tests.Visual.Online } [Test] - public void TestDeleteButtonHiddenWithSingleItem() - { - AddStep("set all players queue mode", () => Client.ChangeSettings(new MultiplayerRoomSettings { QueueMode = QueueMode.AllPlayers })); - AddUntilStep("wait for queue mode change", () => Client.APIRoom?.QueueMode.Value == QueueMode.AllPlayers); - - assertDeleteButtonVisibility(0, false); - - addPlaylistItem(() => API.LocalUser.Value.OnlineID); - assertDeleteButtonVisibility(0, true); - - deleteItem(1); - assertDeleteButtonVisibility(0, false); - } - - [Test] - public void TestDeleteButtonHiddenInHostOnlyMode() - { - AddStep("set all players queue mode", () => Client.ChangeSettings(new MultiplayerRoomSettings { QueueMode = QueueMode.AllPlayers })); - AddUntilStep("wait for queue mode change", () => Client.APIRoom?.QueueMode.Value == QueueMode.AllPlayers); - addPlaylistItem(() => 1234); - - AddStep("set host-only queue mode", () => Client.ChangeSettings(new MultiplayerRoomSettings { QueueMode = QueueMode.HostOnly })); - AddUntilStep("wait for queue mode change", () => Client.APIRoom?.QueueMode.Value == QueueMode.HostOnly); - - assertDeleteButtonVisibility(0, false); - } - - [Test] - public void TestOnlyItemOwnerHasDeleteButton() + public void TestDeleteButtonAlwaysVisibleForHost() { AddStep("set all players queue mode", () => Client.ChangeSettings(new MultiplayerRoomSettings { QueueMode = QueueMode.AllPlayers })); AddUntilStep("wait for queue mode change", () => Client.APIRoom?.QueueMode.Value == QueueMode.AllPlayers); addPlaylistItem(() => API.LocalUser.Value.OnlineID); - assertDeleteButtonVisibility(0, true); assertDeleteButtonVisibility(1, true); + addPlaylistItem(() => 1234); + assertDeleteButtonVisibility(2, true); + } + [Test] + public void TestDeleteButtonOnlyVisibleForItemOwnerIfNotHost() + { + AddStep("set all players queue mode", () => Client.ChangeSettings(new MultiplayerRoomSettings { QueueMode = QueueMode.AllPlayers })); + AddUntilStep("wait for queue mode change", () => Client.APIRoom?.QueueMode.Value == QueueMode.AllPlayers); + + AddStep("join other user", () => Client.AddUser(new APIUser { Id = 1234 })); + AddStep("set other user as host", () => Client.TransferHost(1234)); + + addPlaylistItem(() => API.LocalUser.Value.OnlineID); + assertDeleteButtonVisibility(1, true); addPlaylistItem(() => 1234); assertDeleteButtonVisibility(2, false); + + AddStep("set local user as host", () => Client.TransferHost(API.LocalUser.Value.OnlineID)); + assertDeleteButtonVisibility(1, true); + assertDeleteButtonVisibility(2, true); } [Test] - public void TestNonOwnerDoesNotHaveDeleteButton() + public void TestCurrentItemDoesNotHaveDeleteButton() { AddStep("set all players queue mode", () => Client.ChangeSettings(new MultiplayerRoomSettings { QueueMode = QueueMode.AllPlayers })); AddUntilStep("wait for queue mode change", () => Client.APIRoom?.QueueMode.Value == QueueMode.AllPlayers); - addPlaylistItem(() => 1234); - assertDeleteButtonVisibility(0, true); - assertDeleteButtonVisibility(1, false); - } - - [Test] - public void TestSelectedItemDoesNotHaveDeleteButton() - { - AddStep("set all players queue mode", () => Client.ChangeSettings(new MultiplayerRoomSettings { QueueMode = QueueMode.AllPlayers })); - AddUntilStep("wait for queue mode change", () => Client.APIRoom?.QueueMode.Value == QueueMode.AllPlayers); + assertDeleteButtonVisibility(0, false); addPlaylistItem(() => API.LocalUser.Value.OnlineID); - assertDeleteButtonVisibility(0, true); - - AddStep("set first playlist item as selected", () => playlist.SelectedItem.Value = playlist.Items[0]); assertDeleteButtonVisibility(0, false); + assertDeleteButtonVisibility(1, true); + + // Run through gameplay. + AddStep("set state to ready", () => Client.ChangeUserState(API.LocalUser.Value.Id, MultiplayerUserState.Ready)); + AddUntilStep("local state is ready", () => Client.LocalUser?.State == MultiplayerUserState.Ready); + AddStep("start match", () => Client.StartMatch()); + AddUntilStep("match started", () => Client.LocalUser?.State == MultiplayerUserState.WaitingForLoad); + AddStep("set state to loaded", () => Client.ChangeUserState(API.LocalUser.Value.Id, MultiplayerUserState.Loaded)); + AddUntilStep("local state is playing", () => Client.LocalUser?.State == MultiplayerUserState.Playing); + AddStep("set state to finished play", () => Client.ChangeUserState(API.LocalUser.Value.Id, MultiplayerUserState.FinishedPlay)); + AddUntilStep("local state is results", () => Client.LocalUser?.State == MultiplayerUserState.Results); + + assertDeleteButtonVisibility(1, false); } private void addPlaylistItem(Func userId) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerQueueList.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerQueueList.cs index 72bcf7d343..8832c9bd60 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerQueueList.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerQueueList.cs @@ -8,6 +8,7 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Online.API; +using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.Multiplayer; using osu.Game.Online.Rooms; using osuTK; @@ -29,10 +30,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match.Playlist Spacing = new Vector2(0, 2) }; - protected override DrawableRoomPlaylistItem CreateDrawablePlaylistItem(PlaylistItem item) => new QueuePlaylistItem(item) - { - Items = { BindTarget = Items } - }; + protected override DrawableRoomPlaylistItem CreateDrawablePlaylistItem(PlaylistItem item) => new QueuePlaylistItem(item); private class QueueFillFlowContainer : FillFlowContainer> { @@ -50,14 +48,15 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match.Playlist private class QueuePlaylistItem : DrawableRoomPlaylistItem { - public readonly IBindableList Items = new BindableList(); - [Resolved] private IAPIProvider api { get; set; } [Resolved] private MultiplayerClient multiplayerClient { get; set; } + [Resolved(typeof(Room), nameof(Room.Host))] + private Bindable host { get; set; } + [Resolved(typeof(Room), nameof(Room.QueueMode))] private Bindable queueMode { get; set; } @@ -72,16 +71,14 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match.Playlist RequestDeletion = item => multiplayerClient.RemovePlaylistItem(item.ID); - Items.BindCollectionChanged((_, __) => updateDeleteButtonVisibility()); + host.BindValueChanged(_ => updateDeleteButtonVisibility()); queueMode.BindValueChanged(_ => updateDeleteButtonVisibility()); SelectedItem.BindValueChanged(_ => updateDeleteButtonVisibility(), true); } private void updateDeleteButtonVisibility() { - AllowDeletion = queueMode.Value != QueueMode.HostOnly - && Items.Count > 1 - && Item.OwnerID == api.LocalUser.Value.OnlineID + AllowDeletion = (Item.OwnerID == api.LocalUser.Value.OnlineID || multiplayerClient.IsHost) && SelectedItem.Value != Item; } } From 17d676200bd2ce4c35d2a5642632b59169dc69fb Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 9 Dec 2021 17:33:36 +0900 Subject: [PATCH 039/146] Xmldoc fixes from code review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Bartłomiej Dach --- osu.Game/Screens/OnlinePlay/DrawableRoomPlaylist.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylist.cs b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylist.cs index 8bd2daa2c3..5e180afcf9 100644 --- a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylist.cs +++ b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylist.cs @@ -13,13 +13,13 @@ using osuTK; namespace osu.Game.Screens.OnlinePlay { /// - /// A list scrollable list which displays the s in a . + /// A scrollable list which displays the s in a . /// public class DrawableRoomPlaylist : OsuRearrangeableListContainer { /// - /// The currently-selected item, used to show a border around items. - /// May be updated by playlist items if is true. + /// The currently-selected item. Selection is visually represented with a border. + /// May be updated by clicking playlist items if is true. /// public readonly Bindable SelectedItem = new Bindable(); From 0963b0045303887db0e11e0ef01728ac709adddc Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 9 Dec 2021 17:33:59 +0900 Subject: [PATCH 040/146] Disallow item selection in playlists song select --- .../Screens/OnlinePlay/Components/MatchBeatmapDetailArea.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Components/MatchBeatmapDetailArea.cs b/osu.Game/Screens/OnlinePlay/Components/MatchBeatmapDetailArea.cs index c29b8db26c..89842e933b 100644 --- a/osu.Game/Screens/OnlinePlay/Components/MatchBeatmapDetailArea.cs +++ b/osu.Game/Screens/OnlinePlay/Components/MatchBeatmapDetailArea.cs @@ -46,8 +46,7 @@ namespace osu.Game.Screens.OnlinePlay.Components Padding = new MarginPadding { Bottom = 10 }, Child = playlist = new PlaylistsRoomSettingsPlaylist { - RelativeSizeAxes = Axes.Both, - AllowSelection = true, + RelativeSizeAxes = Axes.Both } } }, From dfe19f350968d3cbf490690261ea940ccb80fa80 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 9 Dec 2021 17:48:09 +0900 Subject: [PATCH 041/146] Minor code reformatting --- .../TestSceneDrawableRoomPlaylist.cs | 27 +++++++++++--- .../OnlinePlay/DrawableRoomPlaylistItem.cs | 36 +++++++++---------- 2 files changed, 41 insertions(+), 22 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs index c9a5552f39..f6c15f314e 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs @@ -62,7 +62,11 @@ namespace osu.Game.Tests.Visual.Multiplayer [Test] public void TestEditable() { - createPlaylist(p => p.AllowReordering = p.AllowDeletion = true); + createPlaylist(p => + { + p.AllowReordering = true; + p.AllowDeletion = true; + }); moveToItem(0); assertHandleVisibility(0, true); @@ -75,7 +79,12 @@ namespace osu.Game.Tests.Visual.Multiplayer [Test] public void TestMarkInvalid() { - createPlaylist(p => p.AllowReordering = p.AllowDeletion = p.AllowSelection = true); + createPlaylist(p => + { + p.AllowReordering = true; + p.AllowDeletion = true; + p.AllowSelection = true; + }); AddStep("mark item 0 as invalid", () => playlist.Items[0].MarkInvalid()); @@ -102,7 +111,12 @@ namespace osu.Game.Tests.Visual.Multiplayer [Test] public void TestEditableSelectable() { - createPlaylist(p => p.AllowReordering = p.AllowDeletion = p.AllowSelection = true); + createPlaylist(p => + { + p.AllowReordering = true; + p.AllowDeletion = true; + p.AllowSelection = true; + }); moveToItem(0); assertHandleVisibility(0, true); @@ -116,7 +130,12 @@ namespace osu.Game.Tests.Visual.Multiplayer [Test] public void TestSelectionNotLostAfterRearrangement() { - createPlaylist(p => p.AllowReordering = p.AllowDeletion = p.AllowSelection = true); + createPlaylist(p => + { + p.AllowReordering = true; + p.AllowDeletion = true; + p.AllowSelection = true; + }); moveToItem(0); AddStep("click", () => InputManager.Click(MouseButton.Left)); diff --git a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs index 7a1c069df3..9ad43fced5 100644 --- a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs +++ b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs @@ -38,7 +38,8 @@ namespace osu.Game.Screens.OnlinePlay public class DrawableRoomPlaylistItem : OsuRearrangeableListItem { public const float HEIGHT = 50; - public const float ICON_HEIGHT = 34; + + private const float icon_height = 34; /// /// Invoked when this item requests to be deleted. @@ -238,7 +239,7 @@ namespace osu.Game.Screens.OnlinePlay } if (Item.Beatmap.Value != null) - difficultyIconContainer.Child = new DifficultyIcon(Item.Beatmap.Value, ruleset.Value, requiredMods, performBackgroundDifficultyLookup: false) { Size = new Vector2(ICON_HEIGHT) }; + difficultyIconContainer.Child = new DifficultyIcon(Item.Beatmap.Value, ruleset.Value, requiredMods, performBackgroundDifficultyLookup: false) { Size = new Vector2(icon_height) }; else difficultyIconContainer.Clear(); @@ -392,7 +393,7 @@ namespace osu.Game.Screens.OnlinePlay { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Size = new Vector2(ICON_HEIGHT), + Size = new Vector2(icon_height), Margin = new MarginPadding { Right = 8 }, Masking = true, CornerRadius = 4, @@ -405,22 +406,21 @@ namespace osu.Game.Screens.OnlinePlay }; } - private IEnumerable createButtons() => - new[] + private IEnumerable createButtons() => new[] + { + showResultsButton = new ShowResultsButton { - showResultsButton = new ShowResultsButton - { - Action = () => RequestResults?.Invoke(Item), - Alpha = AllowShowingResults ? 1 : 0, - }, - Item.Beatmap.Value == null ? Empty() : new PlaylistDownloadButton(Item), - removeButton = new PlaylistRemoveButton - { - Size = new Vector2(30, 30), - Alpha = AllowDeletion ? 1 : 0, - Action = () => RequestDeletion?.Invoke(Item), - }, - }; + Action = () => RequestResults?.Invoke(Item), + Alpha = AllowShowingResults ? 1 : 0, + }, + Item.Beatmap.Value == null ? Empty() : new PlaylistDownloadButton(Item), + removeButton = new PlaylistRemoveButton + { + Size = new Vector2(30, 30), + Alpha = AllowDeletion ? 1 : 0, + Action = () => RequestDeletion?.Invoke(Item), + }, + }; public class PlaylistRemoveButton : GrayButton { From e7e61cd9ab8b882ccb99325960e2b6677b972548 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 9 Dec 2021 18:50:53 +0900 Subject: [PATCH 042/146] Fix potential crash due to children being mutated after disposal This is a bit of an unfortunate edge case where the unbind-on-disposal doesn't help, since the binding is happening in BDL, and the usage is in a nested `LoadComponentAsync` call. Combine those and you have a recipe for disaster. --- osu.Game/Screens/Select/BeatmapInfoWedge.cs | 25 ++++++++++++--------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapInfoWedge.cs b/osu.Game/Screens/Select/BeatmapInfoWedge.cs index e4cf9bd868..d292b7114f 100644 --- a/osu.Game/Screens/Select/BeatmapInfoWedge.cs +++ b/osu.Game/Screens/Select/BeatmapInfoWedge.cs @@ -330,6 +330,21 @@ namespace osu.Game.Screens.Select addInfoLabels(); } + protected override void LoadComplete() + { + base.LoadComplete(); + + mods.BindValueChanged(m => + { + settingChangeTracker?.Dispose(); + + refreshBPMLabel(); + + settingChangeTracker = new ModSettingChangeTracker(m.NewValue); + settingChangeTracker.SettingChanged += _ => refreshBPMLabel(); + }, true); + } + private void setMetadata(string source) { ArtistLabel.Text = artistBinding.Value; @@ -360,16 +375,6 @@ namespace osu.Game.Screens.Select Children = getRulesetInfoLabels() } }; - - mods.BindValueChanged(m => - { - settingChangeTracker?.Dispose(); - - refreshBPMLabel(); - - settingChangeTracker = new ModSettingChangeTracker(m.NewValue); - settingChangeTracker.SettingChanged += _ => refreshBPMLabel(); - }, true); } private InfoLabel[] getRulesetInfoLabels() From a3b53ac2f68a176f8406740b6fe486ac420a6ac4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 9 Dec 2021 18:58:47 +0900 Subject: [PATCH 043/146] Change comparison to match in all locations --- osu.Game/Screens/Play/PlayerLoader.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs index dfc3c2b61d..60843acb4f 100644 --- a/osu.Game/Screens/Play/PlayerLoader.cs +++ b/osu.Game/Screens/Play/PlayerLoader.cs @@ -505,9 +505,9 @@ namespace osu.Game.Screens.Play volumeOverlay.IsMuted.Value = false; // Check values before resetting, as the user may have only had mute enabled, in which case we might not need to adjust volumes. - if (audioManager.Volume.Value < volume_requirement) + if (audioManager.Volume.Value <= volume_requirement) audioManager.Volume.SetDefault(); - if (audioManager.VolumeTrack.Value < volume_requirement) + if (audioManager.VolumeTrack.Value <= volume_requirement) audioManager.VolumeTrack.SetDefault(); return true; From f9af239ed95153275b66310f3caf67215a5ae73e Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 9 Dec 2021 23:46:03 +0900 Subject: [PATCH 044/146] Cleanup duplicated classes in DrawableRoomPlaylistItem --- .../TestSceneDrawableRoomPlaylist.cs | 12 ++++- .../TestScenePlaylistsRoomSettingsPlaylist.cs | 2 +- .../Online/TestSceneMultiplayerQueueList.cs | 4 +- .../OnlinePlay/DrawableRoomPlaylistItem.cs | 46 ++++--------------- 4 files changed, 23 insertions(+), 41 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs index f6c15f314e..d0cf17db1f 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs @@ -248,6 +248,16 @@ namespace osu.Game.Tests.Visual.Multiplayer AddAssert("owner visible", () => playlist.ChildrenOfType().All(a => a.IsPresent == withOwner)); } + [Test] + public void TestWithAllButtonsEnabled() + { + createPlaylist(p => + { + p.AllowDeletion = true; + p.AllowShowingResults = true; + }); + } + private void moveToItem(int index, Vector2? offset = null) => AddStep($"move mouse to item {index}", () => InputManager.MoveMouseTo(playlist.ChildrenOfType().ElementAt(index), offset)); @@ -263,7 +273,7 @@ namespace osu.Game.Tests.Visual.Multiplayer private void assertDeleteButtonVisibility(int index, bool visible) => AddAssert($"delete button {index} {(visible ? "is" : "is not")} visible", - () => (playlist.ChildrenOfType().ElementAt(2 + index * 2).Alpha > 0) == visible); + () => (playlist.ChildrenOfType().ElementAt(2 + index * 2).RemoveButton.Alpha > 0) == visible); private void createPlaylist(Action setupPlaylist = null) { diff --git a/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsRoomSettingsPlaylist.cs b/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsRoomSettingsPlaylist.cs index 93ccd5f1e1..c61b99c2c2 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsRoomSettingsPlaylist.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsRoomSettingsPlaylist.cs @@ -126,7 +126,7 @@ namespace osu.Game.Tests.Visual.Multiplayer private void moveToDeleteButton(int index, Vector2? offset = null) => AddStep($"move mouse to delete button {index}", () => { var item = playlist.ChildrenOfType>().ElementAt(index); - InputManager.MoveMouseTo(item.ChildrenOfType().ElementAt(0), offset); + InputManager.MoveMouseTo(item.ChildrenOfType().ElementAt(0).RemoveButton, offset); }); private void createPlaylist() diff --git a/osu.Game.Tests/Visual/Online/TestSceneMultiplayerQueueList.cs b/osu.Game.Tests/Visual/Online/TestSceneMultiplayerQueueList.cs index 19e5624a04..95cef391f1 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneMultiplayerQueueList.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneMultiplayerQueueList.cs @@ -154,7 +154,7 @@ namespace osu.Game.Tests.Visual.Online AddStep($"move mouse to delete button {index}", () => { item = playlist.ChildrenOfType>().ElementAt(index); - InputManager.MoveMouseTo(item.ChildrenOfType().ElementAt(0)); + InputManager.MoveMouseTo(item.ChildrenOfType().ElementAt(0).RemoveButton); }); AddStep("click", () => InputManager.Click(MouseButton.Left)); @@ -164,6 +164,6 @@ namespace osu.Game.Tests.Visual.Online private void assertDeleteButtonVisibility(int index, bool visible) => AddUntilStep($"delete button {index} {(visible ? "is" : "is not")} visible", - () => (playlist.ChildrenOfType().ElementAt(index).Alpha > 0) == visible); + () => (playlist.ChildrenOfType().ElementAt(index).RemoveButton.Alpha > 0) == visible); } } diff --git a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs index 9ad43fced5..ac5f77a72b 100644 --- a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs +++ b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs @@ -59,6 +59,8 @@ namespace osu.Game.Screens.OnlinePlay public readonly PlaylistItem Item; + public Drawable RemoveButton { get; private set; } + private readonly DelayedLoadWrapper onScreenLoader = new DelayedLoadWrapper(Empty) { RelativeSizeAxes = Axes.Both }; private readonly IBindable valid = new Bindable(); private readonly Bindable beatmap = new Bindable(); @@ -73,7 +75,6 @@ namespace osu.Game.Screens.OnlinePlay private ModDisplay modDisplay; private FillFlowContainer buttonsFlow; private UpdateableAvatar ownerAvatar; - private Drawable removeButton; private Drawable showResultsButton; private PanelBackground panelBackground; private FillFlowContainer mainFillFlow; @@ -191,8 +192,8 @@ namespace osu.Game.Screens.OnlinePlay { allowDeletion = value; - if (removeButton != null) - removeButton.Alpha = value ? 1 : 0; + if (RemoveButton != null) + RemoveButton.Alpha = value ? 1 : 0; } } @@ -408,35 +409,23 @@ namespace osu.Game.Screens.OnlinePlay private IEnumerable createButtons() => new[] { - showResultsButton = new ShowResultsButton + showResultsButton = new GrayButton(FontAwesome.Solid.ChartPie) { + Size = new Vector2(30, 30), Action = () => RequestResults?.Invoke(Item), Alpha = AllowShowingResults ? 1 : 0, + TooltipText = "View results" }, Item.Beatmap.Value == null ? Empty() : new PlaylistDownloadButton(Item), - removeButton = new PlaylistRemoveButton + RemoveButton = new GrayButton(FontAwesome.Solid.MinusSquare) { Size = new Vector2(30, 30), Alpha = AllowDeletion ? 1 : 0, Action = () => RequestDeletion?.Invoke(Item), + TooltipText = "Remove from playlist" }, }; - public class PlaylistRemoveButton : GrayButton - { - public PlaylistRemoveButton() - : base(FontAwesome.Solid.MinusSquare) - { - TooltipText = "Remove from playlist"; - } - - [BackgroundDependencyLoader] - private void load() - { - Icon.Scale = new Vector2(0.8f); - } - } - protected override bool OnClick(ClickEvent e) { if (AllowSelection && valid.Value) @@ -497,23 +486,6 @@ namespace osu.Game.Screens.OnlinePlay } } - private class ShowResultsButton : IconButton - { - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - Icon = FontAwesome.Solid.ChartPie; - TooltipText = "View results"; - - Add(new Box - { - RelativeSizeAxes = Axes.Both, - Depth = float.MaxValue, - Colour = colours.Gray4, - }); - } - } - // For now, this is the same implementation as in PanelBackground, but supports a beatmap info rather than a working beatmap private class PanelBackground : Container // todo: should be a buffered container (https://github.com/ppy/osu-framework/issues/3222) { From 05aa9635a8cee0503e30a5200a37ccd3c80114c9 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 10 Dec 2021 00:38:18 +0900 Subject: [PATCH 045/146] Privatise button again --- .../TestSceneDrawableRoomPlaylist.cs | 2 +- .../TestScenePlaylistsRoomSettingsPlaylist.cs | 2 +- .../Online/TestSceneMultiplayerQueueList.cs | 4 ++-- .../OnlinePlay/DrawableRoomPlaylistItem.cs | 17 ++++++++++++----- 4 files changed, 16 insertions(+), 9 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs index d0cf17db1f..24d7d62aa3 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs @@ -273,7 +273,7 @@ namespace osu.Game.Tests.Visual.Multiplayer private void assertDeleteButtonVisibility(int index, bool visible) => AddAssert($"delete button {index} {(visible ? "is" : "is not")} visible", - () => (playlist.ChildrenOfType().ElementAt(2 + index * 2).RemoveButton.Alpha > 0) == visible); + () => (playlist.ChildrenOfType().ElementAt(2 + index * 2).Alpha > 0) == visible); private void createPlaylist(Action setupPlaylist = null) { diff --git a/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsRoomSettingsPlaylist.cs b/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsRoomSettingsPlaylist.cs index c61b99c2c2..93ccd5f1e1 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsRoomSettingsPlaylist.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsRoomSettingsPlaylist.cs @@ -126,7 +126,7 @@ namespace osu.Game.Tests.Visual.Multiplayer private void moveToDeleteButton(int index, Vector2? offset = null) => AddStep($"move mouse to delete button {index}", () => { var item = playlist.ChildrenOfType>().ElementAt(index); - InputManager.MoveMouseTo(item.ChildrenOfType().ElementAt(0).RemoveButton, offset); + InputManager.MoveMouseTo(item.ChildrenOfType().ElementAt(0), offset); }); private void createPlaylist() diff --git a/osu.Game.Tests/Visual/Online/TestSceneMultiplayerQueueList.cs b/osu.Game.Tests/Visual/Online/TestSceneMultiplayerQueueList.cs index 95cef391f1..19e5624a04 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneMultiplayerQueueList.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneMultiplayerQueueList.cs @@ -154,7 +154,7 @@ namespace osu.Game.Tests.Visual.Online AddStep($"move mouse to delete button {index}", () => { item = playlist.ChildrenOfType>().ElementAt(index); - InputManager.MoveMouseTo(item.ChildrenOfType().ElementAt(0).RemoveButton); + InputManager.MoveMouseTo(item.ChildrenOfType().ElementAt(0)); }); AddStep("click", () => InputManager.Click(MouseButton.Left)); @@ -164,6 +164,6 @@ namespace osu.Game.Tests.Visual.Online private void assertDeleteButtonVisibility(int index, bool visible) => AddUntilStep($"delete button {index} {(visible ? "is" : "is not")} visible", - () => (playlist.ChildrenOfType().ElementAt(index).RemoveButton.Alpha > 0) == visible); + () => (playlist.ChildrenOfType().ElementAt(index).Alpha > 0) == visible); } } diff --git a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs index ac5f77a72b..9640d9db46 100644 --- a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs +++ b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs @@ -59,8 +59,6 @@ namespace osu.Game.Screens.OnlinePlay public readonly PlaylistItem Item; - public Drawable RemoveButton { get; private set; } - private readonly DelayedLoadWrapper onScreenLoader = new DelayedLoadWrapper(Empty) { RelativeSizeAxes = Axes.Both }; private readonly IBindable valid = new Bindable(); private readonly Bindable beatmap = new Bindable(); @@ -76,6 +74,7 @@ namespace osu.Game.Screens.OnlinePlay private FillFlowContainer buttonsFlow; private UpdateableAvatar ownerAvatar; private Drawable showResultsButton; + private Drawable removeButton; private PanelBackground panelBackground; private FillFlowContainer mainFillFlow; @@ -192,8 +191,8 @@ namespace osu.Game.Screens.OnlinePlay { allowDeletion = value; - if (RemoveButton != null) - RemoveButton.Alpha = value ? 1 : 0; + if (removeButton != null) + removeButton.Alpha = value ? 1 : 0; } } @@ -417,7 +416,7 @@ namespace osu.Game.Screens.OnlinePlay TooltipText = "View results" }, Item.Beatmap.Value == null ? Empty() : new PlaylistDownloadButton(Item), - RemoveButton = new GrayButton(FontAwesome.Solid.MinusSquare) + removeButton = new PlaylistRemoveButton { Size = new Vector2(30, 30), Alpha = AllowDeletion ? 1 : 0, @@ -433,6 +432,14 @@ namespace osu.Game.Screens.OnlinePlay return true; } + public class PlaylistRemoveButton : GrayButton + { + public PlaylistRemoveButton() + : base(FontAwesome.Solid.MinusSquare) + { + } + } + private sealed class PlaylistDownloadButton : BeatmapDownloadButton { private readonly PlaylistItem playlistItem; From 4d1c06c0612a76d527d6458d04e1bb87486482be Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 10 Dec 2021 01:03:36 +0900 Subject: [PATCH 046/146] Add support for host enqueueing in TestMultiplayerClient --- .../Multiplayer/TestMultiplayerClient.cs | 35 +++++++++++-------- 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs index f151add430..1516d0e473 100644 --- a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs +++ b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs @@ -309,31 +309,36 @@ namespace osu.Game.Tests.Visual.Multiplayer Debug.Assert(APIRoom != null); Debug.Assert(currentItem != null); + bool isNewAddition = item.ID == 0; + if (Room.Settings.QueueMode == QueueMode.HostOnly && Room.Host?.UserID != LocalUser?.UserID) throw new InvalidOperationException("Local user is not the room host."); item.OwnerID = userId; - switch (Room.Settings.QueueMode) + if (isNewAddition) { - case QueueMode.HostOnly: - // In host-only mode, the current item is re-used. - item.ID = currentItem.ID; - item.PlaylistOrder = currentItem.PlaylistOrder; + await addItem(item).ConfigureAwait(false); + await updateCurrentItem(Room).ConfigureAwait(false); + } + else + { + var existingItem = serverSidePlaylist.SingleOrDefault(i => i.ID == item.ID); - serverSidePlaylist[currentIndex] = item; - await ((IMultiplayerClient)this).PlaylistItemChanged(item).ConfigureAwait(false); + if (existingItem == null) + throw new InvalidOperationException("Attempted to change an item that doesn't exist."); - // Note: Unlike the server, this is the easiest way to update the current item at this point. - await updateCurrentItem(Room, false).ConfigureAwait(false); - break; + if (existingItem.OwnerID != userId && Room.Host?.UserID != LocalUser?.UserID) + throw new InvalidOperationException("Attempted to change an item which is not owned by the user."); - default: - await addItem(item).ConfigureAwait(false); + if (existingItem.Expired) + throw new InvalidOperationException("Attempted to change an item which has already been played."); - // The current item can change as a result of an item being added. For example, if all items earlier in the queue were expired. - await updateCurrentItem(Room).ConfigureAwait(false); - break; + // Ensure the playlist order doesn't change. + item.PlaylistOrder = existingItem.PlaylistOrder; + + serverSidePlaylist[serverSidePlaylist.IndexOf(existingItem)] = item; + await ((IMultiplayerClient)this).PlaylistItemChanged(item).ConfigureAwait(false); } } From 048a4951156593ae4c2a45d019fe15210228ca2c Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 10 Dec 2021 01:08:54 +0900 Subject: [PATCH 047/146] Add edit button to DrawableRoomPlaylistItem --- .../TestSceneDrawableRoomPlaylist.cs | 1 + .../OnlinePlay/DrawableRoomPlaylist.cs | 27 ++++++++++++- .../OnlinePlay/DrawableRoomPlaylistItem.cs | 38 +++++++++++++++++++ 3 files changed, 65 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs index 24d7d62aa3..f9784384fd 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs @@ -255,6 +255,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { p.AllowDeletion = true; p.AllowShowingResults = true; + p.AllowEditing = true; }); } diff --git a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylist.cs b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylist.cs index 5e180afcf9..57bb4253cb 100644 --- a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylist.cs +++ b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylist.cs @@ -33,6 +33,11 @@ namespace osu.Game.Screens.OnlinePlay /// public Action RequestResults; + /// + /// Invoked when an item requests to be edited. + /// + public Action RequestEdit; + private bool allowReordering; /// @@ -104,6 +109,24 @@ namespace osu.Game.Screens.OnlinePlay } } + private bool allowEditing; + + /// + /// Whether to allow items to be edited. + /// If true, requests to edit items may be satisfied via . + /// + public bool AllowEditing + { + get => allowEditing; + set + { + allowEditing = value; + + foreach (var item in ListContainer.OfType()) + item.AllowEditing = value; + } + } + private bool showItemOwners; /// @@ -135,12 +158,14 @@ namespace osu.Game.Screens.OnlinePlay { d.SelectedItem.BindTarget = SelectedItem; d.RequestDeletion = i => RequestDeletion?.Invoke(i); + d.RequestResults = i => RequestResults?.Invoke(i); + d.RequestEdit = i => RequestEdit?.Invoke(i); d.AllowReordering = AllowReordering; d.AllowDeletion = AllowDeletion; d.AllowSelection = AllowSelection; d.AllowShowingResults = AllowShowingResults; + d.AllowEditing = AllowEditing; d.ShowItemOwner = ShowItemOwners; - d.RequestResults = i => RequestResults?.Invoke(i); }); protected virtual DrawableRoomPlaylistItem CreateDrawablePlaylistItem(PlaylistItem item) => new DrawableRoomPlaylistItem(item); diff --git a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs index 9640d9db46..8042f7d772 100644 --- a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs +++ b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs @@ -51,6 +51,11 @@ namespace osu.Game.Screens.OnlinePlay /// public Action RequestResults; + /// + /// Invoked when this item requests to be edited. + /// + public Action RequestEdit; + /// /// The currently-selected item, used to show a border around this item. /// May be updated by this item if is true. @@ -74,6 +79,7 @@ namespace osu.Game.Screens.OnlinePlay private FillFlowContainer buttonsFlow; private UpdateableAvatar ownerAvatar; private Drawable showResultsButton; + private Drawable editButton; private Drawable removeButton; private PanelBackground panelBackground; private FillFlowContainer mainFillFlow; @@ -213,6 +219,23 @@ namespace osu.Game.Screens.OnlinePlay } } + private bool allowEditing; + + /// + /// Whether this item can be edited. + /// + public bool AllowEditing + { + get => allowEditing; + set + { + allowEditing = value; + + if (editButton != null) + editButton.Alpha = value ? 1 : 0; + } + } + private bool showItemOwner; /// @@ -416,6 +439,13 @@ namespace osu.Game.Screens.OnlinePlay TooltipText = "View results" }, Item.Beatmap.Value == null ? Empty() : new PlaylistDownloadButton(Item), + editButton = new PlaylistEditButton + { + Size = new Vector2(30, 30), + Alpha = AllowEditing ? 1 : 0, + Action = () => RequestEdit?.Invoke(Item), + TooltipText = "Edit" + }, removeButton = new PlaylistRemoveButton { Size = new Vector2(30, 30), @@ -432,6 +462,14 @@ namespace osu.Game.Screens.OnlinePlay return true; } + public class PlaylistEditButton : GrayButton + { + public PlaylistEditButton() + : base(FontAwesome.Solid.Edit) + { + } + } + public class PlaylistRemoveButton : GrayButton { public PlaylistRemoveButton() From 671582a9255706334c67f75a0cece57a454be14a Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 10 Dec 2021 01:15:15 +0900 Subject: [PATCH 048/146] Allow host to enqeue items and items to be edited --- .../TestSceneAllPlayersQueueMode.cs | 4 +- .../Multiplayer/TestSceneHostOnlyQueueMode.cs | 24 ++++++++++- .../Multiplayer/TestSceneMultiplayer.cs | 3 +- .../TestSceneMultiplayerMatchSongSelect.cs | 2 +- .../Match/Playlist/MultiplayerPlaylist.cs | 9 +++- .../Match/Playlist/MultiplayerQueueList.cs | 6 ++- .../Multiplayer/MultiplayerMatchSongSelect.cs | 8 +++- .../Multiplayer/MultiplayerMatchSubScreen.cs | 42 +++++++------------ .../OnlinePlay/OnlinePlaySongSelect.cs | 12 +++--- 9 files changed, 67 insertions(+), 43 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneAllPlayersQueueMode.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneAllPlayersQueueMode.cs index ccfae1deef..8373979308 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneAllPlayersQueueMode.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneAllPlayersQueueMode.cs @@ -87,9 +87,9 @@ namespace osu.Game.Tests.Visual.Multiplayer private void addItem(Func beatmap) { - AddStep("click edit button", () => + AddStep("click add button", () => { - InputManager.MoveMouseTo(this.ChildrenOfType().Single().AddOrEditPlaylistButton); + InputManager.MoveMouseTo(this.ChildrenOfType().Single()); InputManager.Click(MouseButton.Left); }); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneHostOnlyQueueMode.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneHostOnlyQueueMode.cs index 1de7289446..ccac3de304 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneHostOnlyQueueMode.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneHostOnlyQueueMode.cs @@ -7,6 +7,7 @@ using NUnit.Framework; using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Online.Multiplayer; +using osu.Game.Screens.OnlinePlay; using osu.Game.Screens.OnlinePlay.Multiplayer; using osuTK.Input; @@ -74,11 +75,19 @@ namespace osu.Game.Tests.Visual.Multiplayer AddUntilStep("api room updated", () => Client.APIRoom?.QueueMode.Value == QueueMode.AllPlayers); } + [Test] + public void TestAddItemsAsHost() + { + addItem(() => OtherBeatmap); + + AddAssert("playlist contains two items", () => Client.APIRoom?.Playlist.Count == 2); + } + private void selectNewItem(Func beatmap) { AddStep("click edit button", () => { - InputManager.MoveMouseTo(this.ChildrenOfType().Single().AddOrEditPlaylistButton); + InputManager.MoveMouseTo(this.ChildrenOfType().First()); InputManager.Click(MouseButton.Left); }); @@ -90,5 +99,18 @@ namespace osu.Game.Tests.Visual.Multiplayer AddUntilStep("wait for return to match", () => CurrentSubScreen is MultiplayerMatchSubScreen); AddUntilStep("selected item is new beatmap", () => Client.CurrentMatchPlayingItem.Value?.Beatmap.Value?.OnlineID == otherBeatmap.OnlineID); } + + private void addItem(Func beatmap) + { + AddStep("click add button", () => + { + InputManager.MoveMouseTo(this.ChildrenOfType().Single()); + InputManager.Click(MouseButton.Left); + }); + + AddUntilStep("wait for song select", () => CurrentSubScreen is Screens.Select.SongSelect select && select.BeatmapSetsLoaded); + AddStep("select other beatmap", () => ((Screens.Select.SongSelect)CurrentSubScreen).FinaliseSelection(beatmap())); + AddUntilStep("wait for return to match", () => CurrentSubScreen is MultiplayerMatchSubScreen); + } } } diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs index 2411f39ae3..5eb0abc830 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs @@ -416,8 +416,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("Enter song select", () => { var currentSubScreen = ((Screens.OnlinePlay.Multiplayer.Multiplayer)multiplayerScreenStack.CurrentScreen).CurrentSubScreen; - - ((MultiplayerMatchSubScreen)currentSubScreen).SelectBeatmap(); + ((MultiplayerMatchSubScreen)currentSubScreen).OpenSongSelection(client.CurrentMatchPlayingItem.Value); }); AddUntilStep("wait for song select", () => this.ChildrenOfType().FirstOrDefault()?.BeatmapSetsLoaded == true); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs index a5229702a8..d671673d3c 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs @@ -179,7 +179,7 @@ namespace osu.Game.Tests.Visual.Multiplayer public new BeatmapCarousel Carousel => base.Carousel; public TestMultiplayerMatchSongSelect(Room room, WorkingBeatmap beatmap = null, RulesetInfo ruleset = null) - : base(room, beatmap, ruleset) + : base(room, null, beatmap, ruleset) { } } diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerPlaylist.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerPlaylist.cs index c3245b550f..4971489769 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerPlaylist.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerPlaylist.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -19,6 +20,11 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match.Playlist { public readonly Bindable DisplayMode = new Bindable(); + /// + /// Invoked when an item requests to be edited. + /// + public Action RequestEdit; + private MultiplayerQueueList queueList; private MultiplayerHistoryList historyList; private bool firstPopulation = true; @@ -46,7 +52,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match.Playlist queueList = new MultiplayerQueueList { RelativeSizeAxes = Axes.Both, - SelectedItem = { BindTarget = SelectedItem } + SelectedItem = { BindTarget = SelectedItem }, + RequestEdit = item => RequestEdit?.Invoke(item) }, historyList = new MultiplayerHistoryList { diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerQueueList.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerQueueList.cs index 8832c9bd60..3e0f663d42 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerQueueList.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerQueueList.cs @@ -78,8 +78,10 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match.Playlist private void updateDeleteButtonVisibility() { - AllowDeletion = (Item.OwnerID == api.LocalUser.Value.OnlineID || multiplayerClient.IsHost) - && SelectedItem.Value != Item; + bool isItemOwner = Item.OwnerID == api.LocalUser.Value.OnlineID || multiplayerClient.IsHost; + + AllowDeletion = isItemOwner && SelectedItem.Value != Item; + AllowEditing = isItemOwner; } } } diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs index 44efef53f5..80a0289ba9 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs @@ -24,17 +24,22 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer [Resolved] private MultiplayerClient client { get; set; } + private readonly PlaylistItem itemToEdit; + private LoadingLayer loadingLayer; /// /// Construct a new instance of multiplayer song select. /// /// The room. + /// The item to be edited. May be null, in which case a new item will be added to the playlist. /// An optional initial beatmap selection to perform. /// An optional initial ruleset selection to perform. - public MultiplayerMatchSongSelect(Room room, WorkingBeatmap beatmap = null, RulesetInfo ruleset = null) + public MultiplayerMatchSongSelect(Room room, PlaylistItem itemToEdit = null, WorkingBeatmap beatmap = null, RulesetInfo ruleset = null) : base(room) { + this.itemToEdit = itemToEdit; + if (beatmap != null || ruleset != null) { Schedule(() => @@ -61,6 +66,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer client.AddPlaylistItem(new MultiplayerPlaylistItem { + ID = itemToEdit?.ID ?? 0, BeatmapID = item.BeatmapID, BeatmapChecksum = item.Beatmap.Value.MD5Hash, RulesetID = item.RulesetID, diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs index 3a25bd7b06..96a9804067 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs @@ -14,7 +14,6 @@ using osu.Framework.Screens; using osu.Framework.Threading; using osu.Game.Beatmaps; using osu.Game.Configuration; -using osu.Game.Graphics.UserInterface; using osu.Game.Online; using osu.Game.Online.Multiplayer; using osu.Game.Online.Rooms; @@ -44,8 +43,6 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer public override string ShortTitle => "room"; - public OsuButton AddOrEditPlaylistButton { get; private set; } - [Resolved] private MultiplayerClient client { get; set; } @@ -57,6 +54,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer [CanBeNull] private IDisposable readyClickOperation; + private AddItemButton addItemButton; + public MultiplayerMatchSubScreen(Room room) : base(room) { @@ -134,12 +133,12 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer new Drawable[] { new OverlinedHeader("Beatmap") }, new Drawable[] { - AddOrEditPlaylistButton = new PurpleTriangleButton + addItemButton = new AddItemButton { RelativeSizeAxes = Axes.X, Height = 40, - Action = SelectBeatmap, - Alpha = 0 + Text = "Add item", + Action = () => OpenSongSelection(null) }, }, null, @@ -147,7 +146,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer { new MultiplayerPlaylist { - RelativeSizeAxes = Axes.Both + RelativeSizeAxes = Axes.Both, + RequestEdit = OpenSongSelection } }, new[] @@ -220,12 +220,12 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer } }; - internal void SelectBeatmap() + internal void OpenSongSelection(PlaylistItem itemToEdit) { if (!this.IsCurrentScreen()) return; - this.Push(new MultiplayerMatchSongSelect(Room)); + this.Push(new MultiplayerMatchSongSelect(Room, itemToEdit)); } protected override Drawable CreateFooter() => new MultiplayerMatchFooter @@ -385,23 +385,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer return; } - switch (client.Room.Settings.QueueMode) - { - case QueueMode.HostOnly: - AddOrEditPlaylistButton.Text = "Edit beatmap"; - AddOrEditPlaylistButton.Alpha = client.IsHost ? 1 : 0; - break; - - case QueueMode.AllPlayers: - case QueueMode.AllPlayersRoundRobin: - AddOrEditPlaylistButton.Text = "Add beatmap"; - AddOrEditPlaylistButton.Alpha = 1; - break; - - default: - AddOrEditPlaylistButton.Alpha = 0; - break; - } + addItemButton.Alpha = client.IsHost || Room.QueueMode.Value != QueueMode.HostOnly ? 1 : 0; Scheduler.AddOnce(UpdateMods); } @@ -466,7 +450,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer return; } - this.Push(new MultiplayerMatchSongSelect(Room, beatmap, ruleset)); + this.Push(new MultiplayerMatchSongSelect(Room, SelectedItem.Value, beatmap, ruleset)); } protected override void Dispose(bool isDisposing) @@ -481,5 +465,9 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer modSettingChangeTracker?.Dispose(); } + + public class AddItemButton : PurpleTriangleButton + { + } } } diff --git a/osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs b/osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs index 4bc0b55433..63957caee3 100644 --- a/osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs +++ b/osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs @@ -33,14 +33,14 @@ namespace osu.Game.Screens.OnlinePlay [Resolved(typeof(Room), nameof(Room.Playlist))] protected BindableList Playlist { get; private set; } + [CanBeNull] + [Resolved(CanBeNull = true)] + protected IBindable SelectedItem { get; private set; } + protected override UserActivity InitialActivity => new UserActivity.InLobby(room); protected readonly Bindable> FreeMods = new Bindable>(Array.Empty()); - [CanBeNull] - [Resolved(CanBeNull = true)] - private IBindable selectedItem { get; set; } - private readonly FreeModSelectOverlay freeModSelectOverlay; private readonly Room room; @@ -80,8 +80,8 @@ namespace osu.Game.Screens.OnlinePlay // At this point, Mods contains both the required and allowed mods. For selection purposes, it should only contain the required mods. // Similarly, freeMods is currently empty but should only contain the allowed mods. - Mods.Value = selectedItem?.Value?.RequiredMods.Select(m => m.DeepClone()).ToArray() ?? Array.Empty(); - FreeMods.Value = selectedItem?.Value?.AllowedMods.Select(m => m.DeepClone()).ToArray() ?? Array.Empty(); + Mods.Value = SelectedItem?.Value?.RequiredMods.Select(m => m.DeepClone()).ToArray() ?? Array.Empty(); + FreeMods.Value = SelectedItem?.Value?.AllowedMods.Select(m => m.DeepClone()).ToArray() ?? Array.Empty(); Mods.BindValueChanged(onModsChanged); Ruleset.BindValueChanged(onRulesetChanged); From a445dcd2c60a0f5456884f8346de75b37717ea2e Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 10 Dec 2021 02:09:31 +0900 Subject: [PATCH 049/146] Fix incorrect test namespace --- .../{Online => Multiplayer}/TestSceneMultiplayerQueueList.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) rename osu.Game.Tests/Visual/{Online => Multiplayer}/TestSceneMultiplayerQueueList.cs (98%) diff --git a/osu.Game.Tests/Visual/Online/TestSceneMultiplayerQueueList.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerQueueList.cs similarity index 98% rename from osu.Game.Tests/Visual/Online/TestSceneMultiplayerQueueList.cs rename to osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerQueueList.cs index 19e5624a04..a2b2da0aec 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneMultiplayerQueueList.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerQueueList.cs @@ -20,11 +20,10 @@ using osu.Game.Rulesets; using osu.Game.Screens.OnlinePlay; using osu.Game.Screens.OnlinePlay.Multiplayer.Match.Playlist; using osu.Game.Tests.Resources; -using osu.Game.Tests.Visual.Multiplayer; using osuTK; using osuTK.Input; -namespace osu.Game.Tests.Visual.Online +namespace osu.Game.Tests.Visual.Multiplayer { public class TestSceneMultiplayerQueueList : MultiplayerTestScene { From 612f47bb9ff7eaf565def5970fc24daf80f7007d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 10 Dec 2021 13:45:29 +0900 Subject: [PATCH 050/146] Add the ability to create playlists of 2 weeks ~ 3 months in duration --- .../Playlists/PlaylistsRoomSettingsOverlay.cs | 52 ++++++++++++++----- 1 file changed, 38 insertions(+), 14 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSettingsOverlay.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSettingsOverlay.cs index 8f31422add..6c8ab52d22 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSettingsOverlay.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSettingsOverlay.cs @@ -5,7 +5,9 @@ using System; using System.Collections.Specialized; using System.Linq; using Humanizer; +using Humanizer.Localisation; using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; @@ -14,6 +16,8 @@ using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; +using osu.Game.Online.API; +using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.Rooms; using osu.Game.Overlays; using osu.Game.Screens.OnlinePlay.Match.Components; @@ -69,6 +73,9 @@ namespace osu.Game.Screens.OnlinePlay.Playlists [Resolved(CanBeNull = true)] private IRoomManager manager { get; set; } + [Resolved] + private IAPIProvider api { get; set; } + private readonly Room room; public MatchSettings(Room room) @@ -134,19 +141,6 @@ namespace osu.Game.Screens.OnlinePlay.Playlists Child = DurationField = new DurationDropdown { RelativeSizeAxes = Axes.X, - Items = new[] - { - TimeSpan.FromMinutes(30), - TimeSpan.FromHours(1), - TimeSpan.FromHours(2), - TimeSpan.FromHours(4), - TimeSpan.FromHours(8), - TimeSpan.FromHours(12), - //TimeSpan.FromHours(16), - TimeSpan.FromHours(24), - TimeSpan.FromDays(3), - TimeSpan.FromDays(7) - } } }, new Section("Allowed attempts (across all playlist items)") @@ -303,10 +297,40 @@ namespace osu.Game.Screens.OnlinePlay.Playlists MaxAttempts.BindValueChanged(count => MaxAttemptsField.Text = count.NewValue?.ToString(), true); Duration.BindValueChanged(duration => DurationField.Current.Value = duration.NewValue ?? TimeSpan.FromMinutes(30), true); + api.LocalUser.BindValueChanged(populateDurations, true); + playlist.Items.BindTo(Playlist); Playlist.BindCollectionChanged(onPlaylistChanged, true); } + private void populateDurations(ValueChangedEvent user) + { + DurationField.Items = new[] + { + TimeSpan.FromMinutes(30), + TimeSpan.FromHours(1), + TimeSpan.FromHours(2), + TimeSpan.FromHours(4), + TimeSpan.FromHours(8), + TimeSpan.FromHours(12), + TimeSpan.FromHours(24), + TimeSpan.FromDays(3), + TimeSpan.FromDays(7), + TimeSpan.FromDays(14), + }; + + // TODO: show these in the interface at all times. + if (user.NewValue.IsSupporter) + { + // roughly correct (see https://github.com/Humanizr/Humanizer/blob/18167e56c082449cc4fe805b8429e3127a7b7f93/readme.md?plain=1#L427) + // if we want this to be more accurate we might consider sending an actual end time, not a time span. probably not required though. + const int days_in_month = 31; + + DurationField.AddDropdownItem(TimeSpan.FromDays(days_in_month)); + DurationField.AddDropdownItem(TimeSpan.FromDays(days_in_month * 3)); + } + } + protected override void Update() { base.Update(); @@ -405,7 +429,7 @@ namespace osu.Game.Screens.OnlinePlay.Playlists Menu.MaxHeight = 100; } - protected override LocalisableString GenerateItemText(TimeSpan item) => item.Humanize(); + protected override LocalisableString GenerateItemText(TimeSpan item) => item.Humanize(maxUnit: TimeUnit.Month); } } } From 9ac8e6c81cfa391b757168f9621f5766a2ea4078 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 10 Dec 2021 13:53:48 +0900 Subject: [PATCH 051/146] Add missing null check before attempting to populate bpm info --- osu.Game/Screens/Select/BeatmapInfoWedge.cs | 31 +++++++++++---------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapInfoWedge.cs b/osu.Game/Screens/Select/BeatmapInfoWedge.cs index d292b7114f..6791565828 100644 --- a/osu.Game/Screens/Select/BeatmapInfoWedge.cs +++ b/osu.Game/Screens/Select/BeatmapInfoWedge.cs @@ -163,7 +163,7 @@ namespace osu.Game.Screens.Select private FillFlowContainer infoLabelContainer; private Container bpmLabelContainer; - private readonly WorkingBeatmap beatmap; + private readonly WorkingBeatmap working; private readonly RulesetInfo ruleset; [Resolved] @@ -171,10 +171,10 @@ namespace osu.Game.Screens.Select private ModSettingChangeTracker settingChangeTracker; - public WedgeInfoText(WorkingBeatmap beatmap, RulesetInfo userRuleset) + public WedgeInfoText(WorkingBeatmap working, RulesetInfo userRuleset) { - this.beatmap = beatmap; - ruleset = userRuleset ?? beatmap.BeatmapInfo.Ruleset; + this.working = working; + ruleset = userRuleset ?? working.BeatmapInfo.Ruleset; } private CancellationTokenSource cancellationSource; @@ -183,8 +183,8 @@ namespace osu.Game.Screens.Select [BackgroundDependencyLoader] private void load(OsuColour colours, LocalisationManager localisation, BeatmapDifficultyCache difficultyCache) { - var beatmapInfo = beatmap.BeatmapInfo; - var metadata = beatmapInfo.Metadata ?? beatmap.BeatmapSetInfo?.Metadata ?? new BeatmapMetadata(); + var beatmapInfo = working.BeatmapInfo; + var metadata = beatmapInfo.Metadata ?? working.BeatmapSetInfo?.Metadata ?? new BeatmapMetadata(); RelativeSizeAxes = Axes.Both; @@ -353,7 +353,7 @@ namespace osu.Game.Screens.Select private void addInfoLabels() { - if (beatmap.Beatmap?.HitObjects?.Any() != true) + if (working.Beatmap?.HitObjects?.Any() != true) return; infoLabelContainer.Children = new Drawable[] @@ -362,7 +362,7 @@ namespace osu.Game.Screens.Select { Name = "Length", CreateIcon = () => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Length), - Content = beatmap.BeatmapInfo.Length.ToFormattedDuration().ToString(), + Content = working.BeatmapInfo.Length.ToFormattedDuration().ToString(), }), bpmLabelContainer = new Container { @@ -386,12 +386,12 @@ namespace osu.Game.Screens.Select try { // Try to get the beatmap with the user's ruleset - playableBeatmap = beatmap.GetPlayableBeatmap(ruleset, Array.Empty()); + playableBeatmap = working.GetPlayableBeatmap(ruleset, Array.Empty()); } catch (BeatmapInvalidForRulesetException) { // Can't be converted to the user's ruleset, so use the beatmap's own ruleset - playableBeatmap = beatmap.GetPlayableBeatmap(beatmap.BeatmapInfo.Ruleset, Array.Empty()); + playableBeatmap = working.GetPlayableBeatmap(working.BeatmapInfo.Ruleset, Array.Empty()); } return playableBeatmap.GetStatistics().Select(s => new InfoLabel(s)).ToArray(); @@ -406,8 +406,9 @@ namespace osu.Game.Screens.Select private void refreshBPMLabel() { - var b = beatmap.Beatmap; - if (b == null) + var beatmap = working.Beatmap; + + if (beatmap == null || bpmLabelContainer == null) return; // this doesn't consider mods which apply variable rates, yet. @@ -415,9 +416,9 @@ namespace osu.Game.Screens.Select foreach (var mod in mods.Value.OfType()) rate = mod.ApplyToRate(0, rate); - double bpmMax = b.ControlPointInfo.BPMMaximum * rate; - double bpmMin = b.ControlPointInfo.BPMMinimum * rate; - double mostCommonBPM = 60000 / b.GetMostCommonBeatLength() * rate; + double bpmMax = beatmap.ControlPointInfo.BPMMaximum * rate; + double bpmMin = beatmap.ControlPointInfo.BPMMinimum * rate; + double mostCommonBPM = 60000 / beatmap.GetMostCommonBeatLength() * rate; string labelText = Precision.AlmostEquals(bpmMin, bpmMax) ? $"{bpmMin:0}" From 88670c3b014ce53d805dc51fd5426ef8540824bb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 10 Dec 2021 14:14:22 +0900 Subject: [PATCH 052/146] Document `OpenSongSelection` and mark null param --- .../OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs index 96a9804067..946c749db3 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs @@ -138,7 +138,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer RelativeSizeAxes = Axes.X, Height = 40, Text = "Add item", - Action = () => OpenSongSelection(null) + Action = () => OpenSongSelection() }, }, null, @@ -220,7 +220,11 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer } }; - internal void OpenSongSelection(PlaylistItem itemToEdit) + /// + /// Opens the song selection screen to add or edit an item. + /// + /// An optional playlist item to edit. If null, a new item will be added instead. + internal void OpenSongSelection([CanBeNull] PlaylistItem itemToEdit = null) { if (!this.IsCurrentScreen()) return; From de0f37b08d60477048a9015d1dcd8bb96e019996 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 10 Dec 2021 14:44:35 +0900 Subject: [PATCH 053/146] Separate editing and adding playlist items --- .../Multiplayer/IMultiplayerRoomServer.cs | 6 ++ .../Online/Multiplayer/MultiplayerClient.cs | 2 + .../Multiplayer/OnlineMultiplayerClient.cs | 8 +++ .../Multiplayer/MultiplayerMatchSongSelect.cs | 9 ++- .../Multiplayer/TestMultiplayerClient.cs | 60 ++++++++++--------- 5 files changed, 54 insertions(+), 31 deletions(-) diff --git a/osu.Game/Online/Multiplayer/IMultiplayerRoomServer.cs b/osu.Game/Online/Multiplayer/IMultiplayerRoomServer.cs index 65467e6ba9..73fda78d00 100644 --- a/osu.Game/Online/Multiplayer/IMultiplayerRoomServer.cs +++ b/osu.Game/Online/Multiplayer/IMultiplayerRoomServer.cs @@ -83,6 +83,12 @@ namespace osu.Game.Online.Multiplayer /// The item to add. Task AddPlaylistItem(MultiplayerPlaylistItem item); + /// + /// Edits an existing playlist item with new values. + /// + /// The item to edit, containing new properties. Must have an ID. + Task EditPlaylistItem(MultiplayerPlaylistItem item); + /// /// Removes an item from the playlist. /// diff --git a/osu.Game/Online/Multiplayer/MultiplayerClient.cs b/osu.Game/Online/Multiplayer/MultiplayerClient.cs index 34dc7ea5ea..55b4def908 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerClient.cs @@ -335,6 +335,8 @@ namespace osu.Game.Online.Multiplayer public abstract Task AddPlaylistItem(MultiplayerPlaylistItem item); + public abstract Task EditPlaylistItem(MultiplayerPlaylistItem item); + public abstract Task RemovePlaylistItem(long playlistItemId); Task IMultiplayerClient.RoomStateChanged(MultiplayerRoomState state) diff --git a/osu.Game/Online/Multiplayer/OnlineMultiplayerClient.cs b/osu.Game/Online/Multiplayer/OnlineMultiplayerClient.cs index 7314603603..d268d2bf69 100644 --- a/osu.Game/Online/Multiplayer/OnlineMultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/OnlineMultiplayerClient.cs @@ -162,6 +162,14 @@ namespace osu.Game.Online.Multiplayer return connection.InvokeAsync(nameof(IMultiplayerServer.AddPlaylistItem), item); } + public override Task EditPlaylistItem(MultiplayerPlaylistItem item) + { + if (!IsConnected.Value) + return Task.CompletedTask; + + return connection.InvokeAsync(nameof(IMultiplayerServer.EditPlaylistItem), item); + } + public override Task RemovePlaylistItem(long playlistItemId) { if (!IsConnected.Value) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs index 80a0289ba9..8d3686dd6d 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs @@ -4,6 +4,7 @@ using System; using System.Diagnostics; using System.Linq; +using System.Threading.Tasks; using Microsoft.AspNetCore.SignalR; using osu.Framework.Allocation; using osu.Framework.Logging; @@ -64,7 +65,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer { loadingLayer.Show(); - client.AddPlaylistItem(new MultiplayerPlaylistItem + var multiplayerItem = new MultiplayerPlaylistItem { ID = itemToEdit?.ID ?? 0, BeatmapID = item.BeatmapID, @@ -72,7 +73,11 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer RulesetID = item.RulesetID, RequiredMods = item.RequiredMods.Select(m => new APIMod(m)).ToArray(), AllowedMods = item.AllowedMods.Select(m => new APIMod(m)).ToArray() - }).ContinueWith(t => + }; + + Task task = itemToEdit != null ? client.EditPlaylistItem(multiplayerItem) : client.AddPlaylistItem(multiplayerItem); + + task.ContinueWith(t => { Schedule(() => { diff --git a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs index 1516d0e473..d22f0415e6 100644 --- a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs +++ b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs @@ -309,49 +309,51 @@ namespace osu.Game.Tests.Visual.Multiplayer Debug.Assert(APIRoom != null); Debug.Assert(currentItem != null); - bool isNewAddition = item.ID == 0; - if (Room.Settings.QueueMode == QueueMode.HostOnly && Room.Host?.UserID != LocalUser?.UserID) throw new InvalidOperationException("Local user is not the room host."); item.OwnerID = userId; - if (isNewAddition) - { - await addItem(item).ConfigureAwait(false); - await updateCurrentItem(Room).ConfigureAwait(false); - } - else - { - var existingItem = serverSidePlaylist.SingleOrDefault(i => i.ID == item.ID); - - if (existingItem == null) - throw new InvalidOperationException("Attempted to change an item that doesn't exist."); - - if (existingItem.OwnerID != userId && Room.Host?.UserID != LocalUser?.UserID) - throw new InvalidOperationException("Attempted to change an item which is not owned by the user."); - - if (existingItem.Expired) - throw new InvalidOperationException("Attempted to change an item which has already been played."); - - // Ensure the playlist order doesn't change. - item.PlaylistOrder = existingItem.PlaylistOrder; - - serverSidePlaylist[serverSidePlaylist.IndexOf(existingItem)] = item; - await ((IMultiplayerClient)this).PlaylistItemChanged(item).ConfigureAwait(false); - } + await addItem(item).ConfigureAwait(false); + await updateCurrentItem(Room).ConfigureAwait(false); } public override Task AddPlaylistItem(MultiplayerPlaylistItem item) => AddUserPlaylistItem(api.LocalUser.Value.OnlineID, item); + public async Task EditUserPlaylistItem(int userId, MultiplayerPlaylistItem item) + { + Debug.Assert(Room != null); + Debug.Assert(APIRoom != null); + Debug.Assert(currentItem != null); + + item.OwnerID = userId; + + var existingItem = serverSidePlaylist.SingleOrDefault(i => i.ID == item.ID); + + if (existingItem == null) + throw new InvalidOperationException("Attempted to change an item that doesn't exist."); + + if (existingItem.OwnerID != userId && Room.Host?.UserID != LocalUser?.UserID) + throw new InvalidOperationException("Attempted to change an item which is not owned by the user."); + + if (existingItem.Expired) + throw new InvalidOperationException("Attempted to change an item which has already been played."); + + // Ensure the playlist order doesn't change. + item.PlaylistOrder = existingItem.PlaylistOrder; + + serverSidePlaylist[serverSidePlaylist.IndexOf(existingItem)] = item; + + await ((IMultiplayerClient)this).PlaylistItemChanged(item).ConfigureAwait(false); + } + + public override Task EditPlaylistItem(MultiplayerPlaylistItem item) => EditUserPlaylistItem(api.LocalUser.Value.OnlineID, item); + public async Task RemoveUserPlaylistItem(int userId, long playlistItemId) { Debug.Assert(Room != null); Debug.Assert(APIRoom != null); - if (Room.Settings.QueueMode == QueueMode.HostOnly) - throw new InvalidOperationException("Items cannot be removed in host-only mode."); - var item = serverSidePlaylist.Find(i => i.ID == playlistItemId); if (item == null) From 261847bbec333933dd4f848976f78ba3b58fdd35 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 10 Dec 2021 15:29:31 +0900 Subject: [PATCH 054/146] Avoid touching `ScoreInfo.User` directly --- osu.Game.Tests/Visual/Ranking/TestSceneScorePanelList.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneScorePanelList.cs b/osu.Game.Tests/Visual/Ranking/TestSceneScorePanelList.cs index b7b7407428..2f9652d354 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneScorePanelList.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneScorePanelList.cs @@ -159,8 +159,8 @@ namespace osu.Game.Tests.Visual.Ranking var firstScore = new TestScoreInfo(new OsuRuleset().RulesetInfo); var secondScore = new TestScoreInfo(new OsuRuleset().RulesetInfo); - firstScore.User.Username = "A"; - secondScore.User.Username = "B"; + firstScore.UserString = "A"; + secondScore.UserString = "B"; createListStep(() => new ScorePanelList()); From bf1418bafc6a3d61ad03ccaa6c00f4f9e125b4d9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 10 Dec 2021 15:28:41 +0900 Subject: [PATCH 055/146] Use `OnlineID` instead of legacy IDs for equality and lookups --- osu.Game.Tests/Scores/IO/ImportScoreTest.cs | 2 +- .../Visual/Playlists/TestScenePlaylistsResultsScreen.cs | 9 +++++---- .../Visual/UserInterface/TestSceneDeleteLocalScore.cs | 4 ++-- osu.Game/Online/Leaderboards/LeaderboardScore.cs | 2 +- osu.Game/OsuGame.cs | 4 ++-- osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs | 2 +- osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs | 2 +- osu.Game/Scoring/ScoreInfo.cs | 4 ++-- osu.Game/Scoring/ScoreManager.cs | 2 +- osu.Game/Scoring/ScoreModelManager.cs | 2 +- .../OnlinePlay/Playlists/PlaylistsResultsScreen.cs | 4 ++-- osu.Game/Screens/Play/SpectatorPlayer.cs | 2 +- osu.Game/Screens/Ranking/ScorePanelList.cs | 2 +- osu.Game/Screens/Ranking/SoloResultsScreen.cs | 2 +- 14 files changed, 22 insertions(+), 21 deletions(-) diff --git a/osu.Game.Tests/Scores/IO/ImportScoreTest.cs b/osu.Game.Tests/Scores/IO/ImportScoreTest.cs index 0dee0f89ea..4a01117031 100644 --- a/osu.Game.Tests/Scores/IO/ImportScoreTest.cs +++ b/osu.Game.Tests/Scores/IO/ImportScoreTest.cs @@ -52,7 +52,7 @@ namespace osu.Game.Tests.Scores.IO Assert.AreEqual(toImport.Combo, imported.Combo); Assert.AreEqual(toImport.User.Username, imported.User.Username); Assert.AreEqual(toImport.Date, imported.Date); - Assert.AreEqual(toImport.OnlineScoreID, imported.OnlineScoreID); + Assert.AreEqual(toImport.OnlineID, imported.OnlineID); } finally { diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs index 4284bc6358..38dd507e00 100644 --- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs +++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs @@ -30,6 +30,7 @@ namespace osu.Game.Tests.Visual.Playlists private const int scores_per_result = 10; private TestResultsScreen resultsScreen; + private int currentScoreId; private bool requestComplete; private int totalCount; @@ -37,7 +38,7 @@ namespace osu.Game.Tests.Visual.Playlists [SetUp] public void Setup() => Schedule(() => { - currentScoreId = 0; + currentScoreId = 1; requestComplete = false; totalCount = 0; bindHandler(); @@ -56,7 +57,7 @@ namespace osu.Game.Tests.Visual.Playlists createResults(() => userScore); - AddAssert("user score selected", () => this.ChildrenOfType().Single(p => p.Score.OnlineScoreID == userScore.OnlineScoreID).State == PanelState.Expanded); + AddAssert("user score selected", () => this.ChildrenOfType().Single(p => p.Score.OnlineID == userScore.OnlineScoreID).State == PanelState.Expanded); } [Test] @@ -81,7 +82,7 @@ namespace osu.Game.Tests.Visual.Playlists createResults(() => userScore); AddAssert("more than 1 panel displayed", () => this.ChildrenOfType().Count() > 1); - AddAssert("user score selected", () => this.ChildrenOfType().Single(p => p.Score.OnlineScoreID == userScore.OnlineScoreID).State == PanelState.Expanded); + AddAssert("user score selected", () => this.ChildrenOfType().Single(p => p.Score.OnlineID == userScore.OnlineScoreID).State == PanelState.Expanded); } [Test] @@ -230,7 +231,7 @@ namespace osu.Game.Tests.Visual.Playlists { var multiplayerUserScore = new MultiplayerScore { - ID = (int)(userScore.OnlineScoreID ?? currentScoreId++), + ID = (int)(userScore.OnlineID > 0 ? userScore.OnlineID : currentScoreId++), Accuracy = userScore.Accuracy, EndedAt = userScore.Date, Passed = userScore.Passed, diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs index 9f0f4a6b8b..d22eff9e75 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs @@ -163,7 +163,7 @@ namespace osu.Game.Tests.Visual.UserInterface }); AddUntilStep("wait for fetch", () => leaderboard.Scores != null); - AddUntilStep("score removed from leaderboard", () => leaderboard.Scores.All(s => s.OnlineScoreID != scoreBeingDeleted.OnlineScoreID)); + AddUntilStep("score removed from leaderboard", () => leaderboard.Scores.All(s => s.OnlineID != scoreBeingDeleted.OnlineID)); } [Test] @@ -171,7 +171,7 @@ namespace osu.Game.Tests.Visual.UserInterface { AddStep("delete top score", () => scoreManager.Delete(importedScores[0])); AddUntilStep("wait for fetch", () => leaderboard.Scores != null); - AddUntilStep("score removed from leaderboard", () => leaderboard.Scores.All(s => s.OnlineScoreID != importedScores[0].OnlineScoreID)); + AddUntilStep("score removed from leaderboard", () => leaderboard.Scores.All(s => s.OnlineID != importedScores[0].OnlineID)); } } } diff --git a/osu.Game/Online/Leaderboards/LeaderboardScore.cs b/osu.Game/Online/Leaderboards/LeaderboardScore.cs index 644c2e2a99..14eec8b388 100644 --- a/osu.Game/Online/Leaderboards/LeaderboardScore.cs +++ b/osu.Game/Online/Leaderboards/LeaderboardScore.cs @@ -111,7 +111,7 @@ namespace osu.Game.Online.Leaderboards background = new Box { RelativeSizeAxes = Axes.Both, - Colour = user.Id == api.LocalUser.Value.Id && allowHighlight ? colour.Green : Color4.Black, + Colour = user.OnlineID == api.LocalUser.Value.Id && allowHighlight ? colour.Green : Color4.Black, Alpha = background_alpha, }, }, diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 047c3b4225..565ee7e71d 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -487,8 +487,8 @@ namespace osu.Game // to ensure all the required data for presenting a replay are present. ScoreInfo databasedScoreInfo = null; - if (score.OnlineScoreID != null) - databasedScoreInfo = ScoreManager.Query(s => s.OnlineScoreID == score.OnlineScoreID); + if (score.OnlineID > 0) + databasedScoreInfo = ScoreManager.Query(s => s.OnlineID == score.OnlineID); databasedScoreInfo ??= ScoreManager.Query(s => s.Hash == score.Hash); diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs index 2fcdc9402d..695661d5c9 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs @@ -94,7 +94,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores topScoresContainer.Add(new DrawableTopScore(topScore)); - if (userScoreInfo != null && userScoreInfo.OnlineScoreID != topScore.OnlineScoreID) + if (userScoreInfo != null && userScoreInfo.OnlineID != topScore.OnlineID) topScoresContainer.Add(new DrawableTopScore(userScoreInfo, userScore.Position)); }), TaskContinuationOptions.OnlyOnRanToCompletion); }); diff --git a/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs b/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs index f943422389..b46c1a27ed 100644 --- a/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs +++ b/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs @@ -84,7 +84,7 @@ namespace osu.Game.Scoring.Legacy else if (version >= 20121008) scoreInfo.OnlineScoreID = sr.ReadInt32(); - if (scoreInfo.OnlineScoreID <= 0) + if (scoreInfo.OnlineID <= 0) scoreInfo.OnlineScoreID = null; if (compressedReplay?.Length > 0) diff --git a/osu.Game/Scoring/ScoreInfo.cs b/osu.Game/Scoring/ScoreInfo.cs index 5dc88d7644..ba20ec030a 100644 --- a/osu.Game/Scoring/ScoreInfo.cs +++ b/osu.Game/Scoring/ScoreInfo.cs @@ -237,8 +237,8 @@ namespace osu.Game.Scoring if (ID != 0 && other.ID != 0) return ID == other.ID; - if (OnlineScoreID.HasValue && other.OnlineScoreID.HasValue) - return OnlineScoreID == other.OnlineScoreID; + if (OnlineID > 0) + return OnlineID == other.OnlineID; if (!string.IsNullOrEmpty(Hash) && !string.IsNullOrEmpty(other.Hash)) return Hash == other.Hash; diff --git a/osu.Game/Scoring/ScoreManager.cs b/osu.Game/Scoring/ScoreManager.cs index e9cd44ae83..6de6b57066 100644 --- a/osu.Game/Scoring/ScoreManager.cs +++ b/osu.Game/Scoring/ScoreManager.cs @@ -71,7 +71,7 @@ namespace osu.Game.Scoring return scores.Select((score, index) => (score, totalScore: totalScores[index])) .OrderByDescending(g => g.totalScore) - .ThenBy(g => g.score.OnlineScoreID) + .ThenBy(g => g.score.OnlineID) .Select(g => g.score) .ToArray(); } diff --git a/osu.Game/Scoring/ScoreModelManager.cs b/osu.Game/Scoring/ScoreModelManager.cs index 2cbd3aded7..44f0fe4fdf 100644 --- a/osu.Game/Scoring/ScoreModelManager.cs +++ b/osu.Game/Scoring/ScoreModelManager.cs @@ -66,6 +66,6 @@ namespace osu.Game.Scoring protected override bool CheckLocalAvailability(ScoreInfo model, IQueryable items) => base.CheckLocalAvailability(model, items) - || (model.OnlineScoreID != null && items.Any(i => i.OnlineScoreID == model.OnlineScoreID)); + || (model.OnlineID > 0 && items.Any(i => i.OnlineID == model.OnlineID)); } } diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsResultsScreen.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsResultsScreen.cs index aed3635cbc..67727ef784 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsResultsScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsResultsScreen.cs @@ -186,12 +186,12 @@ namespace osu.Game.Screens.OnlinePlay.Playlists Schedule(() => { // Prefer selecting the local user's score, or otherwise default to the first visible score. - SelectedScore.Value = scoreInfos.FirstOrDefault(s => s.User.Id == api.LocalUser.Value.Id) ?? scoreInfos.FirstOrDefault(); + SelectedScore.Value = scoreInfos.FirstOrDefault(s => s.User.OnlineID == api.LocalUser.Value.Id) ?? scoreInfos.FirstOrDefault(); }); } // Invoke callback to add the scores. Exclude the user's current score which was added previously. - callback.Invoke(scoreInfos.Where(s => s.OnlineScoreID != Score?.OnlineScoreID)); + callback.Invoke(scoreInfos.Where(s => s.OnlineID != Score?.OnlineID)); hideLoadingSpinners(pivot); })); diff --git a/osu.Game/Screens/Play/SpectatorPlayer.cs b/osu.Game/Screens/Play/SpectatorPlayer.cs index f6a89e7fa9..d42643c416 100644 --- a/osu.Game/Screens/Play/SpectatorPlayer.cs +++ b/osu.Game/Screens/Play/SpectatorPlayer.cs @@ -54,7 +54,7 @@ namespace osu.Game.Screens.Play private void userSentFrames(int userId, FrameDataBundle bundle) { - if (userId != score.ScoreInfo.User.Id) + if (userId != score.ScoreInfo.User.OnlineID) return; if (!LoadedBeatmapSuccessfully) diff --git a/osu.Game/Screens/Ranking/ScorePanelList.cs b/osu.Game/Screens/Ranking/ScorePanelList.cs index 22be91b974..f3de48dcf0 100644 --- a/osu.Game/Screens/Ranking/ScorePanelList.cs +++ b/osu.Game/Screens/Ranking/ScorePanelList.cs @@ -341,7 +341,7 @@ namespace osu.Game.Screens.Ranking private IEnumerable applySorting(IEnumerable drawables) => drawables.OfType() .OrderByDescending(GetLayoutPosition) - .ThenBy(s => s.Panel.Score.OnlineScoreID); + .ThenBy(s => s.Panel.Score.OnlineID); } private class Scroll : OsuScrollContainer diff --git a/osu.Game/Screens/Ranking/SoloResultsScreen.cs b/osu.Game/Screens/Ranking/SoloResultsScreen.cs index 929bda6508..afebc728b4 100644 --- a/osu.Game/Screens/Ranking/SoloResultsScreen.cs +++ b/osu.Game/Screens/Ranking/SoloResultsScreen.cs @@ -31,7 +31,7 @@ namespace osu.Game.Screens.Ranking return null; getScoreRequest = new GetScoresRequest(Score.BeatmapInfo, Score.Ruleset); - getScoreRequest.Success += r => scoresCallback?.Invoke(r.Scores.Where(s => s.OnlineID != Score.OnlineScoreID).Select(s => s.CreateScoreInfo(rulesets, Beatmap.Value.BeatmapInfo))); + getScoreRequest.Success += r => scoresCallback?.Invoke(r.Scores.Where(s => s.OnlineID != Score.OnlineID).Select(s => s.CreateScoreInfo(rulesets, Beatmap.Value.BeatmapInfo))); return getScoreRequest; } From 7ac63485ef03d40ae987ae27eb7d25832ed3d97d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 10 Dec 2021 15:31:35 +0900 Subject: [PATCH 056/146] Add setter for `ScoreInfo.OnlineID` --- osu.Game/Scoring/ScoreInfo.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/osu.Game/Scoring/ScoreInfo.cs b/osu.Game/Scoring/ScoreInfo.cs index ba20ec030a..d6b7b2712b 100644 --- a/osu.Game/Scoring/ScoreInfo.cs +++ b/osu.Game/Scoring/ScoreInfo.cs @@ -248,7 +248,12 @@ namespace osu.Game.Scoring #region Implementation of IHasOnlineID - public long OnlineID => OnlineScoreID ?? -1; + [NotMapped] + public long OnlineID + { + get => OnlineScoreID ?? -1; + set => OnlineScoreID = value; + } #endregion From dbb08f7d463aca7cb0d6e8206766f4aaf53821c2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 10 Dec 2021 15:37:12 +0900 Subject: [PATCH 057/146] Use `OnlineID` for set operations --- osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs | 2 +- osu.Game.Tests/Database/BeatmapImporterTests.cs | 2 +- osu.Game.Tests/Scores/IO/ImportScoreTest.cs | 6 +++--- .../Visual/Multiplayer/TestSceneMultiplayerResults.cs | 2 +- .../Multiplayer/TestSceneMultiplayerTeamResults.cs | 2 +- .../Visual/Navigation/TestScenePresentScore.cs | 2 +- .../Playlists/TestScenePlaylistsResultsScreen.cs | 10 +++++----- .../Visual/UserInterface/TestSceneDeleteLocalScore.cs | 2 +- osu.Game/Online/API/Requests/Responses/APIScoreInfo.cs | 2 +- osu.Game/Online/Rooms/MultiplayerScore.cs | 2 +- osu.Game/Online/ScoreDownloadTracker.cs | 2 +- osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs | 7 ++----- osu.Game/Screens/Play/Player.cs | 6 +++--- osu.Game/Screens/Play/SubmittingPlayer.cs | 2 +- 14 files changed, 23 insertions(+), 26 deletions(-) diff --git a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs index 6e2b9d20a8..6d0d5702e9 100644 --- a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs +++ b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs @@ -1022,7 +1022,7 @@ namespace osu.Game.Tests.Beatmaps.IO { return ImportScoreTest.LoadScoreIntoOsu(osu, new ScoreInfo { - OnlineScoreID = 2, + OnlineID = 2, BeatmapInfo = beatmapInfo, BeatmapInfoID = beatmapInfo.ID }, new ImportScoreTest.TestArchiveReader()); diff --git a/osu.Game.Tests/Database/BeatmapImporterTests.cs b/osu.Game.Tests/Database/BeatmapImporterTests.cs index a6edd6cb5f..e47e24021f 100644 --- a/osu.Game.Tests/Database/BeatmapImporterTests.cs +++ b/osu.Game.Tests/Database/BeatmapImporterTests.cs @@ -809,7 +809,7 @@ namespace osu.Game.Tests.Database // TODO: reimplement when we have score support in realm. // return ImportScoreTest.LoadScoreIntoOsu(osu, new ScoreInfo // { - // OnlineScoreID = 2, + // OnlineID = 2, // Beatmap = beatmap, // BeatmapInfoID = beatmap.ID // }, new ImportScoreTest.TestArchiveReader()); diff --git a/osu.Game.Tests/Scores/IO/ImportScoreTest.cs b/osu.Game.Tests/Scores/IO/ImportScoreTest.cs index 4a01117031..bbc92b7817 100644 --- a/osu.Game.Tests/Scores/IO/ImportScoreTest.cs +++ b/osu.Game.Tests/Scores/IO/ImportScoreTest.cs @@ -40,7 +40,7 @@ namespace osu.Game.Tests.Scores.IO Combo = 250, User = new APIUser { Username = "Test user" }, Date = DateTimeOffset.Now, - OnlineScoreID = 12345, + OnlineID = 12345, }; var imported = await LoadScoreIntoOsu(osu, toImport); @@ -163,12 +163,12 @@ namespace osu.Game.Tests.Scores.IO { var osu = LoadOsuIntoHost(host, true); - await LoadScoreIntoOsu(osu, new ScoreInfo { OnlineScoreID = 2 }, new TestArchiveReader()); + await LoadScoreIntoOsu(osu, new ScoreInfo { OnlineID = 2 }, new TestArchiveReader()); var scoreManager = osu.Dependencies.Get(); // Note: A new score reference is used here since the import process mutates the original object to set an ID - Assert.That(scoreManager.IsAvailableLocally(new ScoreInfo { OnlineScoreID = 2 })); + Assert.That(scoreManager.IsAvailableLocally(new ScoreInfo { OnlineID = 2 })); } finally { diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerResults.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerResults.cs index 80f807e7d3..744a2d187d 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerResults.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerResults.cs @@ -33,7 +33,7 @@ namespace osu.Game.Tests.Visual.Multiplayer BeatmapInfo = beatmapInfo, User = new APIUser { Username = "Test user" }, Date = DateTimeOffset.Now, - OnlineScoreID = 12345, + OnlineID = 12345, Ruleset = rulesetInfo, }; diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerTeamResults.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerTeamResults.cs index da1fa226e1..99b6edc3b6 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerTeamResults.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerTeamResults.cs @@ -37,7 +37,7 @@ namespace osu.Game.Tests.Visual.Multiplayer BeatmapInfo = beatmapInfo, User = new APIUser { Username = "Test user" }, Date = DateTimeOffset.Now, - OnlineScoreID = 12345, + OnlineID = 12345, Ruleset = rulesetInfo, }; diff --git a/osu.Game.Tests/Visual/Navigation/TestScenePresentScore.cs b/osu.Game.Tests/Visual/Navigation/TestScenePresentScore.cs index c9dec25ad3..1653247570 100644 --- a/osu.Game.Tests/Visual/Navigation/TestScenePresentScore.cs +++ b/osu.Game.Tests/Visual/Navigation/TestScenePresentScore.cs @@ -128,7 +128,7 @@ namespace osu.Game.Tests.Visual.Navigation imported = Game.ScoreManager.Import(new ScoreInfo { Hash = Guid.NewGuid().ToString(), - OnlineScoreID = i, + OnlineID = i, BeatmapInfo = beatmap.Beatmaps.First(), Ruleset = ruleset ?? new OsuRuleset().RulesetInfo }).Result.Value; diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs index 38dd507e00..4ac3b7c733 100644 --- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs +++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs @@ -51,13 +51,13 @@ namespace osu.Game.Tests.Visual.Playlists AddStep("bind user score info handler", () => { - userScore = new TestScoreInfo(new OsuRuleset().RulesetInfo) { OnlineScoreID = currentScoreId++ }; + userScore = new TestScoreInfo(new OsuRuleset().RulesetInfo) { OnlineID = currentScoreId++ }; bindHandler(userScore: userScore); }); createResults(() => userScore); - AddAssert("user score selected", () => this.ChildrenOfType().Single(p => p.Score.OnlineID == userScore.OnlineScoreID).State == PanelState.Expanded); + AddAssert("user score selected", () => this.ChildrenOfType().Single(p => p.Score.OnlineID == userScore.OnlineID).State == PanelState.Expanded); } [Test] @@ -75,14 +75,14 @@ namespace osu.Game.Tests.Visual.Playlists AddStep("bind user score info handler", () => { - userScore = new TestScoreInfo(new OsuRuleset().RulesetInfo) { OnlineScoreID = currentScoreId++ }; + userScore = new TestScoreInfo(new OsuRuleset().RulesetInfo) { OnlineID = currentScoreId++ }; bindHandler(true, userScore); }); createResults(() => userScore); AddAssert("more than 1 panel displayed", () => this.ChildrenOfType().Count() > 1); - AddAssert("user score selected", () => this.ChildrenOfType().Single(p => p.Score.OnlineID == userScore.OnlineScoreID).State == PanelState.Expanded); + AddAssert("user score selected", () => this.ChildrenOfType().Single(p => p.Score.OnlineID == userScore.OnlineID).State == PanelState.Expanded); } [Test] @@ -124,7 +124,7 @@ namespace osu.Game.Tests.Visual.Playlists AddStep("bind user score info handler", () => { - userScore = new TestScoreInfo(new OsuRuleset().RulesetInfo) { OnlineScoreID = currentScoreId++ }; + userScore = new TestScoreInfo(new OsuRuleset().RulesetInfo) { OnlineID = currentScoreId++ }; bindHandler(userScore: userScore); }); diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs index d22eff9e75..2363bbbfcf 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs @@ -91,7 +91,7 @@ namespace osu.Game.Tests.Visual.UserInterface { var score = new ScoreInfo { - OnlineScoreID = i, + OnlineID = i, BeatmapInfo = beatmapInfo, BeatmapInfoID = beatmapInfo.ID, Accuracy = RNG.NextDouble(), diff --git a/osu.Game/Online/API/Requests/Responses/APIScoreInfo.cs b/osu.Game/Online/API/Requests/Responses/APIScoreInfo.cs index 467d5a9f23..057e98c421 100644 --- a/osu.Game/Online/API/Requests/Responses/APIScoreInfo.cs +++ b/osu.Game/Online/API/Requests/Responses/APIScoreInfo.cs @@ -101,7 +101,7 @@ namespace osu.Game.Online.API.Requests.Responses BeatmapInfo = beatmap, User = User, Accuracy = Accuracy, - OnlineScoreID = OnlineID, + OnlineID = OnlineID, Date = Date, PP = PP, RulesetID = RulesetID, diff --git a/osu.Game/Online/Rooms/MultiplayerScore.cs b/osu.Game/Online/Rooms/MultiplayerScore.cs index 7bc3377ad9..05c9a1b6cf 100644 --- a/osu.Game/Online/Rooms/MultiplayerScore.cs +++ b/osu.Game/Online/Rooms/MultiplayerScore.cs @@ -69,7 +69,7 @@ namespace osu.Game.Online.Rooms var scoreInfo = new ScoreInfo { - OnlineScoreID = ID, + OnlineID = ID, TotalScore = TotalScore, MaxCombo = MaxCombo, BeatmapInfo = beatmap, diff --git a/osu.Game/Online/ScoreDownloadTracker.cs b/osu.Game/Online/ScoreDownloadTracker.cs index e09cc7c9cd..6320b7ff97 100644 --- a/osu.Game/Online/ScoreDownloadTracker.cs +++ b/osu.Game/Online/ScoreDownloadTracker.cs @@ -35,7 +35,7 @@ namespace osu.Game.Online var scoreInfo = new ScoreInfo { ID = TrackedItem.ID, - OnlineScoreID = TrackedItem.OnlineScoreID + OnlineID = TrackedItem.OnlineID }; if (Manager.IsAvailableLocally(scoreInfo)) diff --git a/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs b/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs index b46c1a27ed..fefee370b9 100644 --- a/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs +++ b/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs @@ -80,12 +80,9 @@ namespace osu.Game.Scoring.Legacy byte[] compressedReplay = sr.ReadByteArray(); if (version >= 20140721) - scoreInfo.OnlineScoreID = sr.ReadInt64(); + scoreInfo.OnlineID = sr.ReadInt64(); else if (version >= 20121008) - scoreInfo.OnlineScoreID = sr.ReadInt32(); - - if (scoreInfo.OnlineID <= 0) - scoreInfo.OnlineScoreID = null; + scoreInfo.OnlineID = sr.ReadInt32(); if (compressedReplay?.Length > 0) { diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index a0e9428cff..521cf7d1e9 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -1031,13 +1031,13 @@ namespace osu.Game.Screens.Play // // Until we better define the server-side logic behind this, let's not store the online ID to avoid potential unique constraint // conflicts across various systems (ie. solo and multiplayer). - long? onlineScoreId = score.ScoreInfo.OnlineScoreID; - score.ScoreInfo.OnlineScoreID = null; + long onlineScoreId = score.ScoreInfo.OnlineID; + score.ScoreInfo.OnlineID = -1; await scoreManager.Import(score.ScoreInfo, replayReader).ConfigureAwait(false); // ... And restore the online ID for other processes to handle correctly (e.g. de-duplication for the results screen). - score.ScoreInfo.OnlineScoreID = onlineScoreId; + score.ScoreInfo.OnlineID = onlineScoreId; } /// diff --git a/osu.Game/Screens/Play/SubmittingPlayer.cs b/osu.Game/Screens/Play/SubmittingPlayer.cs index c07cfa9c4d..c613167908 100644 --- a/osu.Game/Screens/Play/SubmittingPlayer.cs +++ b/osu.Game/Screens/Play/SubmittingPlayer.cs @@ -156,7 +156,7 @@ namespace osu.Game.Screens.Play request.Success += s => { - score.ScoreInfo.OnlineScoreID = s.ID; + score.ScoreInfo.OnlineID = s.ID; score.ScoreInfo.Position = s.Position; scoreSubmissionSource.SetResult(true); From bff02bedbfc28a94cab1f87a8e423c2fd653c29f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 10 Dec 2021 16:01:41 +0900 Subject: [PATCH 058/146] Rename `APIScoreInfo` to `APIScore` --- .../Gameplay/TestSceneReplayDownloadButton.cs | 2 +- .../Visual/Online/TestSceneScoresContainer.cs | 20 +++++++++---------- .../Online/TestSceneUserProfileScores.cs | 8 ++++---- .../API/Requests/GetUserScoresRequest.cs | 2 +- .../{APIScoreInfo.cs => APIScore.cs} | 2 +- .../Responses/APIScoreWithPosition.cs | 2 +- .../Requests/Responses/APIScoresCollection.cs | 2 +- osu.Game/Online/Solo/SubmittableScore.cs | 2 +- .../Sections/Ranks/DrawableProfileScore.cs | 4 ++-- .../Ranks/DrawableProfileWeightedScore.cs | 2 +- .../Sections/Ranks/PaginatedScoreContainer.cs | 8 ++++---- 11 files changed, 27 insertions(+), 27 deletions(-) rename osu.Game/Online/API/Requests/Responses/{APIScoreInfo.cs => APIScore.cs} (99%) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs index f47fae33ca..42c4f89e9d 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs @@ -164,7 +164,7 @@ namespace osu.Game.Tests.Visual.Gameplay private ScoreInfo getScoreInfo(bool replayAvailable) { - return new APIScoreInfo + return new APIScore { OnlineID = 2553163309, RulesetID = 0, diff --git a/osu.Game.Tests/Visual/Online/TestSceneScoresContainer.cs b/osu.Game.Tests/Visual/Online/TestSceneScoresContainer.cs index 50969aad9b..be2db9a8a0 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneScoresContainer.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneScoresContainer.cs @@ -47,9 +47,9 @@ namespace osu.Game.Tests.Visual.Online var allScores = new APIScoresCollection { - Scores = new List + Scores = new List { - new APIScoreInfo + new APIScore { User = new APIUser { @@ -74,7 +74,7 @@ namespace osu.Game.Tests.Visual.Online TotalScore = 1234567890, Accuracy = 1, }, - new APIScoreInfo + new APIScore { User = new APIUser { @@ -98,7 +98,7 @@ namespace osu.Game.Tests.Visual.Online TotalScore = 1234789, Accuracy = 0.9997, }, - new APIScoreInfo + new APIScore { User = new APIUser { @@ -121,7 +121,7 @@ namespace osu.Game.Tests.Visual.Online TotalScore = 12345678, Accuracy = 0.9854, }, - new APIScoreInfo + new APIScore { User = new APIUser { @@ -143,7 +143,7 @@ namespace osu.Game.Tests.Visual.Online TotalScore = 1234567, Accuracy = 0.8765, }, - new APIScoreInfo + new APIScore { User = new APIUser { @@ -166,7 +166,7 @@ namespace osu.Game.Tests.Visual.Online var myBestScore = new APIScoreWithPosition { - Score = new APIScoreInfo + Score = new APIScore { User = new APIUser { @@ -189,7 +189,7 @@ namespace osu.Game.Tests.Visual.Online var myBestScoreWithNullPosition = new APIScoreWithPosition { - Score = new APIScoreInfo + Score = new APIScore { User = new APIUser { @@ -212,9 +212,9 @@ namespace osu.Game.Tests.Visual.Online var oneScore = new APIScoresCollection { - Scores = new List + Scores = new List { - new APIScoreInfo + new APIScore { User = new APIUser { diff --git a/osu.Game.Tests/Visual/Online/TestSceneUserProfileScores.cs b/osu.Game.Tests/Visual/Online/TestSceneUserProfileScores.cs index 9c2cc13416..7dfdca8276 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneUserProfileScores.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneUserProfileScores.cs @@ -19,7 +19,7 @@ namespace osu.Game.Tests.Visual.Online { public TestSceneUserProfileScores() { - var firstScore = new APIScoreInfo + var firstScore = new APIScore { PP = 1047.21, Rank = ScoreRank.SH, @@ -42,7 +42,7 @@ namespace osu.Game.Tests.Visual.Online Accuracy = 0.9813 }; - var secondScore = new APIScoreInfo + var secondScore = new APIScore { PP = 134.32, Rank = ScoreRank.A, @@ -64,7 +64,7 @@ namespace osu.Game.Tests.Visual.Online Accuracy = 0.998546 }; - var thirdScore = new APIScoreInfo + var thirdScore = new APIScore { PP = 96.83, Rank = ScoreRank.S, @@ -81,7 +81,7 @@ namespace osu.Game.Tests.Visual.Online Accuracy = 0.9726 }; - var noPPScore = new APIScoreInfo + var noPPScore = new APIScore { Rank = ScoreRank.B, Beatmap = new APIBeatmap diff --git a/osu.Game/Online/API/Requests/GetUserScoresRequest.cs b/osu.Game/Online/API/Requests/GetUserScoresRequest.cs index e13ac8e539..653abf7427 100644 --- a/osu.Game/Online/API/Requests/GetUserScoresRequest.cs +++ b/osu.Game/Online/API/Requests/GetUserScoresRequest.cs @@ -8,7 +8,7 @@ using osu.Game.Rulesets; namespace osu.Game.Online.API.Requests { - public class GetUserScoresRequest : PaginatedAPIRequest> + public class GetUserScoresRequest : PaginatedAPIRequest> { private readonly long userId; private readonly ScoreType type; diff --git a/osu.Game/Online/API/Requests/Responses/APIScoreInfo.cs b/osu.Game/Online/API/Requests/Responses/APIScore.cs similarity index 99% rename from osu.Game/Online/API/Requests/Responses/APIScoreInfo.cs rename to osu.Game/Online/API/Requests/Responses/APIScore.cs index 057e98c421..bd99a05a88 100644 --- a/osu.Game/Online/API/Requests/Responses/APIScoreInfo.cs +++ b/osu.Game/Online/API/Requests/Responses/APIScore.cs @@ -16,7 +16,7 @@ using osu.Game.Scoring.Legacy; namespace osu.Game.Online.API.Requests.Responses { - public class APIScoreInfo : IScoreInfo + public class APIScore : IScoreInfo { [JsonProperty(@"score")] public long TotalScore { get; set; } diff --git a/osu.Game/Online/API/Requests/Responses/APIScoreWithPosition.cs b/osu.Game/Online/API/Requests/Responses/APIScoreWithPosition.cs index 48b7134901..d3c9ba0c7e 100644 --- a/osu.Game/Online/API/Requests/Responses/APIScoreWithPosition.cs +++ b/osu.Game/Online/API/Requests/Responses/APIScoreWithPosition.cs @@ -14,7 +14,7 @@ namespace osu.Game.Online.API.Requests.Responses public int? Position; [JsonProperty(@"score")] - public APIScoreInfo Score; + public APIScore Score; public ScoreInfo CreateScoreInfo(RulesetStore rulesets, BeatmapInfo beatmap = null) { diff --git a/osu.Game/Online/API/Requests/Responses/APIScoresCollection.cs b/osu.Game/Online/API/Requests/Responses/APIScoresCollection.cs index 5304664bf8..283ebf2411 100644 --- a/osu.Game/Online/API/Requests/Responses/APIScoresCollection.cs +++ b/osu.Game/Online/API/Requests/Responses/APIScoresCollection.cs @@ -9,7 +9,7 @@ namespace osu.Game.Online.API.Requests.Responses public class APIScoresCollection { [JsonProperty(@"scores")] - public List Scores; + public List Scores; [JsonProperty(@"userScore")] public APIScoreWithPosition UserScore; diff --git a/osu.Game/Online/Solo/SubmittableScore.cs b/osu.Game/Online/Solo/SubmittableScore.cs index 373c302844..5ca5ad9619 100644 --- a/osu.Game/Online/Solo/SubmittableScore.cs +++ b/osu.Game/Online/Solo/SubmittableScore.cs @@ -16,7 +16,7 @@ namespace osu.Game.Online.Solo { /// /// A class specifically for sending scores to the API during score submission. - /// This is used instead of due to marginally different serialisation naming requirements. + /// This is used instead of due to marginally different serialisation naming requirements. /// [Serializable] public class SubmittableScore diff --git a/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileScore.cs b/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileScore.cs index fb464e1b41..562be0403e 100644 --- a/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileScore.cs +++ b/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileScore.cs @@ -28,7 +28,7 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks private const float performance_background_shear = 0.45f; - protected readonly APIScoreInfo Score; + protected readonly APIScore Score; [Resolved] private OsuColour colours { get; set; } @@ -36,7 +36,7 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks [Resolved] private OverlayColourProvider colourProvider { get; set; } - public DrawableProfileScore(APIScoreInfo score) + public DrawableProfileScore(APIScore score) { Score = score; diff --git a/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileWeightedScore.cs b/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileWeightedScore.cs index e653be5cfa..78ae0a5634 100644 --- a/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileWeightedScore.cs +++ b/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileWeightedScore.cs @@ -16,7 +16,7 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks { private readonly double weight; - public DrawableProfileWeightedScore(APIScoreInfo score, double weight) + public DrawableProfileWeightedScore(APIScore score, double weight) : base(score) { this.weight = weight; diff --git a/osu.Game/Overlays/Profile/Sections/Ranks/PaginatedScoreContainer.cs b/osu.Game/Overlays/Profile/Sections/Ranks/PaginatedScoreContainer.cs index c3f10587a9..5532e35cc5 100644 --- a/osu.Game/Overlays/Profile/Sections/Ranks/PaginatedScoreContainer.cs +++ b/osu.Game/Overlays/Profile/Sections/Ranks/PaginatedScoreContainer.cs @@ -15,7 +15,7 @@ using APIUser = osu.Game.Online.API.Requests.Responses.APIUser; namespace osu.Game.Overlays.Profile.Sections.Ranks { - public class PaginatedScoreContainer : PaginatedProfileSubsection + public class PaginatedScoreContainer : PaginatedProfileSubsection { private readonly ScoreType type; @@ -51,7 +51,7 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks } } - protected override void OnItemsReceived(List items) + protected override void OnItemsReceived(List items) { if (VisiblePages == 0) drawableItemIndex = 0; @@ -59,12 +59,12 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks base.OnItemsReceived(items); } - protected override APIRequest> CreateRequest() => + protected override APIRequest> CreateRequest() => new GetUserScoresRequest(User.Value.Id, type, VisiblePages++, ItemsPerPage); private int drawableItemIndex; - protected override Drawable CreateDrawableItem(APIScoreInfo model) + protected override Drawable CreateDrawableItem(APIScore model) { switch (type) { From c6d0d6451d463767f60400143c6e195ed1c8f363 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 11 Nov 2021 23:18:31 +0900 Subject: [PATCH 059/146] Change `IScoreInfo.User` to an interface type --- osu.Game/Online/API/Requests/Responses/APIScore.cs | 6 ++++++ osu.Game/Scoring/IScoreInfo.cs | 4 ++-- osu.Game/Scoring/ScoreInfo.cs | 2 ++ 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/osu.Game/Online/API/Requests/Responses/APIScore.cs b/osu.Game/Online/API/Requests/Responses/APIScore.cs index bd99a05a88..4f795bee6c 100644 --- a/osu.Game/Online/API/Requests/Responses/APIScore.cs +++ b/osu.Game/Online/API/Requests/Responses/APIScore.cs @@ -13,6 +13,7 @@ using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; using osu.Game.Scoring; using osu.Game.Scoring.Legacy; +using osu.Game.Users; namespace osu.Game.Online.API.Requests.Responses { @@ -150,6 +151,11 @@ namespace osu.Game.Online.API.Requests.Responses public IRulesetInfo Ruleset => new RulesetInfo { OnlineID = RulesetID }; IEnumerable IHasNamedFiles.Files => throw new NotImplementedException(); + #region Implementation of IScoreInfo + IBeatmapInfo IScoreInfo.Beatmap => Beatmap; + IUser IScoreInfo.User => User; + + #endregion } } diff --git a/osu.Game/Scoring/IScoreInfo.cs b/osu.Game/Scoring/IScoreInfo.cs index 8b5b228632..b4ad183cd3 100644 --- a/osu.Game/Scoring/IScoreInfo.cs +++ b/osu.Game/Scoring/IScoreInfo.cs @@ -4,14 +4,14 @@ using System; using osu.Game.Beatmaps; using osu.Game.Database; -using osu.Game.Online.API.Requests.Responses; using osu.Game.Rulesets; +using osu.Game.Users; namespace osu.Game.Scoring { public interface IScoreInfo : IHasOnlineID, IHasNamedFiles { - APIUser User { get; } + IUser User { get; } long TotalScore { get; } diff --git a/osu.Game/Scoring/ScoreInfo.cs b/osu.Game/Scoring/ScoreInfo.cs index d6b7b2712b..7729e01389 100644 --- a/osu.Game/Scoring/ScoreInfo.cs +++ b/osu.Game/Scoring/ScoreInfo.cs @@ -14,6 +14,7 @@ using osu.Game.Online.API.Requests.Responses; using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Scoring; +using osu.Game.Users; using osu.Game.Utils; namespace osu.Game.Scoring @@ -261,6 +262,7 @@ namespace osu.Game.Scoring IBeatmapInfo IScoreInfo.Beatmap => BeatmapInfo; IRulesetInfo IScoreInfo.Ruleset => Ruleset; + IUser IScoreInfo.User => User; bool IScoreInfo.HasReplay => Files.Any(); #endregion From 970a9ae0748d8d6f99fd6035c5f96673702665a3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 10 Dec 2021 17:22:24 +0900 Subject: [PATCH 060/146] Add update thread asserts to `RoomManager` methods --- osu.Game/Screens/OnlinePlay/Components/RoomManager.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/osu.Game/Screens/OnlinePlay/Components/RoomManager.cs b/osu.Game/Screens/OnlinePlay/Components/RoomManager.cs index 02565c6ebe..6979b5bc30 100644 --- a/osu.Game/Screens/OnlinePlay/Components/RoomManager.cs +++ b/osu.Game/Screens/OnlinePlay/Components/RoomManager.cs @@ -7,11 +7,13 @@ using System.Diagnostics; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Development; using osu.Framework.Graphics; using osu.Framework.Logging; using osu.Game.Online.API; using osu.Game.Online.Rooms; using osu.Game.Rulesets; +using osu.Game.Utils; namespace osu.Game.Screens.OnlinePlay.Components { @@ -107,6 +109,7 @@ namespace osu.Game.Screens.OnlinePlay.Components public void AddOrUpdateRoom(Room room) { + Debug.Assert(ThreadSafety.IsUpdateThread); Debug.Assert(room.RoomID.Value != null); if (ignoredRooms.Contains(room.RoomID.Value.Value)) @@ -136,12 +139,16 @@ namespace osu.Game.Screens.OnlinePlay.Components public void RemoveRoom(Room room) { + Debug.Assert(ThreadSafety.IsUpdateThread); + rooms.Remove(room); notifyRoomsUpdated(); } public void ClearRooms() { + Debug.Assert(ThreadSafety.IsUpdateThread); + rooms.Clear(); notifyRoomsUpdated(); } From 73227c084e380d893bad1bb9a1d6e0fefcecccb2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 10 Dec 2021 17:42:40 +0900 Subject: [PATCH 061/146] 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 0c922c09ac..5cf59decec 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 adb25f46fe..6f01cc65fe 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 db5d9af865..fdb63a19d3 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -60,7 +60,7 @@ - + @@ -83,7 +83,7 @@ - + From e98060ac37571471fa1477bd0038b64d219525db Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 10 Dec 2021 17:55:18 +0900 Subject: [PATCH 062/146] Remove unused using --- osu.Game/Screens/OnlinePlay/Components/RoomManager.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/Components/RoomManager.cs b/osu.Game/Screens/OnlinePlay/Components/RoomManager.cs index 6979b5bc30..238aa4059d 100644 --- a/osu.Game/Screens/OnlinePlay/Components/RoomManager.cs +++ b/osu.Game/Screens/OnlinePlay/Components/RoomManager.cs @@ -13,7 +13,6 @@ using osu.Framework.Logging; using osu.Game.Online.API; using osu.Game.Online.Rooms; using osu.Game.Rulesets; -using osu.Game.Utils; namespace osu.Game.Screens.OnlinePlay.Components { From 5f6e887be71bc8ebd5894f07669958d3e4445dd0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 10 Dec 2021 18:11:52 +0900 Subject: [PATCH 063/146] Remove `OnlineID` comparison from `ScoreInfo.Equals` This matches the implementation we have for `BeatmapInfo` and `BeatmapSetInfo`. All comparisons of `OnlineID` should be done directly using them (ie. how it's done in `ScoreModelDownloader`). --- osu.Game.Tests/Scores/IO/TestScoreEquality.cs | 18 ------------------ osu.Game/Scoring/ScoreInfo.cs | 12 +++--------- 2 files changed, 3 insertions(+), 27 deletions(-) diff --git a/osu.Game.Tests/Scores/IO/TestScoreEquality.cs b/osu.Game.Tests/Scores/IO/TestScoreEquality.cs index d1374eb6e5..42fcb3acab 100644 --- a/osu.Game.Tests/Scores/IO/TestScoreEquality.cs +++ b/osu.Game.Tests/Scores/IO/TestScoreEquality.cs @@ -44,24 +44,6 @@ namespace osu.Game.Tests.Scores.IO Assert.That(score1, Is.EqualTo(score2)); } - [Test] - public void TestNonMatchingByHash() - { - ScoreInfo score1 = new ScoreInfo { Hash = "a" }; - ScoreInfo score2 = new ScoreInfo { Hash = "b" }; - - Assert.That(score1, Is.Not.EqualTo(score2)); - } - - [Test] - public void TestMatchingByHash() - { - ScoreInfo score1 = new ScoreInfo { Hash = "a" }; - ScoreInfo score2 = new ScoreInfo { Hash = "a" }; - - Assert.That(score1, Is.EqualTo(score2)); - } - [Test] public void TestNonMatchingByNull() { diff --git a/osu.Game/Scoring/ScoreInfo.cs b/osu.Game/Scoring/ScoreInfo.cs index 7729e01389..7c2d882f91 100644 --- a/osu.Game/Scoring/ScoreInfo.cs +++ b/osu.Game/Scoring/ScoreInfo.cs @@ -232,19 +232,13 @@ namespace osu.Game.Scoring public bool Equals(ScoreInfo other) { - if (other == null) - return false; + if (ReferenceEquals(this, other)) return true; + if (other == null) return false; if (ID != 0 && other.ID != 0) return ID == other.ID; - if (OnlineID > 0) - return OnlineID == other.OnlineID; - - if (!string.IsNullOrEmpty(Hash) && !string.IsNullOrEmpty(other.Hash)) - return Hash == other.Hash; - - return ReferenceEquals(this, other); + return false; } #region Implementation of IHasOnlineID From c9f6c5c673aaff4ae53d4cce9ffb2832e49c31b4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 10 Dec 2021 18:32:06 +0900 Subject: [PATCH 064/146] Add `MatchesOnlineID` implementation for `IScoreInfo` --- osu.Game/Extensions/ModelExtensions.cs | 8 ++++++++ osu.Game/Online/ScoreDownloadTracker.cs | 3 ++- osu.Game/Scoring/ScoreModelDownloader.cs | 3 ++- 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/osu.Game/Extensions/ModelExtensions.cs b/osu.Game/Extensions/ModelExtensions.cs index 2274da0fd4..f178a5c97b 100644 --- a/osu.Game/Extensions/ModelExtensions.cs +++ b/osu.Game/Extensions/ModelExtensions.cs @@ -104,6 +104,14 @@ namespace osu.Game.Extensions /// Whether online IDs match. If either instance is missing an online ID, this will return false. public static bool MatchesOnlineID(this APIUser? instance, APIUser? other) => matchesOnlineID(instance, other); + /// + /// Check whether the online ID of two s match. + /// + /// The instance to compare. + /// The other instance to compare against. + /// Whether online IDs match. If either instance is missing an online ID, this will return false. + public static bool MatchesOnlineID(this IScoreInfo? instance, IScoreInfo? other) => matchesOnlineID(instance, other); + private static bool matchesOnlineID(this IHasOnlineID? instance, IHasOnlineID? other) { if (instance == null || other == null) diff --git a/osu.Game/Online/ScoreDownloadTracker.cs b/osu.Game/Online/ScoreDownloadTracker.cs index 6320b7ff97..68932cc388 100644 --- a/osu.Game/Online/ScoreDownloadTracker.cs +++ b/osu.Game/Online/ScoreDownloadTracker.cs @@ -3,6 +3,7 @@ using System; using osu.Framework.Allocation; +using osu.Game.Extensions; using osu.Game.Online.API; using osu.Game.Scoring; @@ -113,7 +114,7 @@ namespace osu.Game.Online UpdateState(DownloadState.NotDownloaded); }); - private bool checkEquality(IScoreInfo x, IScoreInfo y) => x.OnlineID == y.OnlineID; + private bool checkEquality(IScoreInfo x, IScoreInfo y) => x.MatchesOnlineID(y); #region Disposal diff --git a/osu.Game/Scoring/ScoreModelDownloader.cs b/osu.Game/Scoring/ScoreModelDownloader.cs index 038a4bc351..514b7a57de 100644 --- a/osu.Game/Scoring/ScoreModelDownloader.cs +++ b/osu.Game/Scoring/ScoreModelDownloader.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using osu.Game.Database; +using osu.Game.Extensions; using osu.Game.Online.API; using osu.Game.Online.API.Requests; @@ -17,6 +18,6 @@ namespace osu.Game.Scoring protected override ArchiveDownloadRequest CreateDownloadRequest(IScoreInfo score, bool minimiseDownload) => new DownloadReplayRequest(score); public override ArchiveDownloadRequest GetExistingDownload(IScoreInfo model) - => CurrentDownloads.Find(r => r.Model.OnlineID == model.OnlineID); + => CurrentDownloads.Find(r => r.Model.MatchesOnlineID(model)); } } From f7c5a3f506815eee95fc36e62fb88e411319f8ca Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 10 Dec 2021 18:32:32 +0900 Subject: [PATCH 065/146] Use similar method of consuming `OnlineID` as done in beatmap classes --- osu.Game/Database/OsuDbContext.cs | 2 +- osu.Game/Scoring/ScoreInfo.cs | 16 +++++++++------- osu.Game/Screens/Play/Player.cs | 2 +- 3 files changed, 11 insertions(+), 9 deletions(-) diff --git a/osu.Game/Database/OsuDbContext.cs b/osu.Game/Database/OsuDbContext.cs index 13d362e0be..7cd9ae2885 100644 --- a/osu.Game/Database/OsuDbContext.cs +++ b/osu.Game/Database/OsuDbContext.cs @@ -147,7 +147,7 @@ namespace osu.Game.Database modelBuilder.Entity().HasOne(b => b.BaseDifficulty); - modelBuilder.Entity().HasIndex(b => b.OnlineScoreID).IsUnique(); + modelBuilder.Entity().HasIndex(b => b.OnlineID).IsUnique(); } private class OsuDbLoggerFactory : ILoggerFactory diff --git a/osu.Game/Scoring/ScoreInfo.cs b/osu.Game/Scoring/ScoreInfo.cs index 7c2d882f91..7acc7bd055 100644 --- a/osu.Game/Scoring/ScoreInfo.cs +++ b/osu.Game/Scoring/ScoreInfo.cs @@ -137,7 +137,14 @@ namespace osu.Game.Scoring [Column("Beatmap")] public BeatmapInfo BeatmapInfo { get; set; } - public long? OnlineScoreID { get; set; } + private long? onlineID; + + [Column("OnlineScoreID")] + public long? OnlineID + { + get => onlineID; + set => onlineID = value > 0 ? value : null; + } public DateTimeOffset Date { get; set; } @@ -243,12 +250,7 @@ namespace osu.Game.Scoring #region Implementation of IHasOnlineID - [NotMapped] - public long OnlineID - { - get => OnlineScoreID ?? -1; - set => OnlineScoreID = value; - } + long IHasOnlineID.OnlineID => OnlineID ?? -1; #endregion diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 521cf7d1e9..e2896c174f 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -1031,7 +1031,7 @@ namespace osu.Game.Screens.Play // // Until we better define the server-side logic behind this, let's not store the online ID to avoid potential unique constraint // conflicts across various systems (ie. solo and multiplayer). - long onlineScoreId = score.ScoreInfo.OnlineID; + long? onlineScoreId = score.ScoreInfo.OnlineID; score.ScoreInfo.OnlineID = -1; await scoreManager.Import(score.ScoreInfo, replayReader).ConfigureAwait(false); From 3b899af061f2e3f450bbec9913c32c6167681b53 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 10 Dec 2021 18:37:57 +0900 Subject: [PATCH 066/146] Update libraries --- .config/dotnet-tools.json | 4 ++-- osu.Game/osu.Game.csproj | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json index 6444127594..985fc09df3 100644 --- a/.config/dotnet-tools.json +++ b/.config/dotnet-tools.json @@ -27,10 +27,10 @@ ] }, "ppy.localisationanalyser.tools": { - "version": "2021.725.0", + "version": "2021.1210.0", "commands": [ "localisation" ] } } -} \ No newline at end of file +} diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 6f01cc65fe..46064e320b 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -20,7 +20,7 @@ - + @@ -31,15 +31,15 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive - - + + From 5e9510be3608a3718c9b0d109cc8ae80f6b44add Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 10 Dec 2021 18:57:33 +0900 Subject: [PATCH 067/146] Add test coverage of editor resetting mods on entering --- osu.Game.Tests/Visual/Editing/TestSceneEditorTestGameplay.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorTestGameplay.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorTestGameplay.cs index 160af47a6d..50794f15ed 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorTestGameplay.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorTestGameplay.cs @@ -9,6 +9,7 @@ using osu.Framework.Screens; using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Rulesets; +using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu; using osu.Game.Screens.Backgrounds; using osu.Game.Screens.Edit; @@ -44,6 +45,7 @@ namespace osu.Game.Tests.Visual.Editing protected override void LoadEditor() { Beatmap.Value = beatmaps.GetWorkingBeatmap(importedBeatmapSet.Beatmaps.First(b => b.RulesetID == 0)); + SelectedMods.Value = new[] { new ModCinema() }; base.LoadEditor(); } @@ -67,6 +69,7 @@ namespace osu.Game.Tests.Visual.Editing var background = this.ChildrenOfType().Single(); return background.Colour == Color4.DarkGray && background.BlurAmount.Value == 0; }); + AddAssert("no mods selected", () => SelectedMods.Value.Count == 0); } [Test] From c1b3ee6bb23765a540708dd5af9f13064f310786 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 10 Dec 2021 18:57:43 +0900 Subject: [PATCH 068/146] Fix editor not resetting mods when entering Would leave the user potentially in a test mode that is in a weird state (ie. if cinema mod was enabled). Eventually we'll add the ability to choose mods for test play, but that will be done in a saner way. Closes #15870. --- osu.Game/Screens/Edit/EditorLoader.cs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/osu.Game/Screens/Edit/EditorLoader.cs b/osu.Game/Screens/Edit/EditorLoader.cs index 2a01a5b6b2..27ae8ae497 100644 --- a/osu.Game/Screens/Edit/EditorLoader.cs +++ b/osu.Game/Screens/Edit/EditorLoader.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Graphics; @@ -9,6 +10,7 @@ using osu.Framework.Screens; using osu.Framework.Threading; using osu.Game.Beatmaps; using osu.Game.Graphics.UserInterface; +using osu.Game.Rulesets.Mods; using osu.Game.Screens.Menu; using osu.Game.Screens.Play; @@ -53,6 +55,14 @@ namespace osu.Game.Screens.Edit }); } + protected override void LoadComplete() + { + base.LoadComplete(); + + // will be restored via lease, see `DisallowExternalBeatmapRulesetChanges`. + Mods.Value = ArraySegment.Empty; + } + protected virtual Editor CreateEditor() => new Editor(this); protected override void LogoArriving(OsuLogo logo, bool resuming) From 5a953f38119cdca4a3a6a214110b2d2f934ab013 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 10 Dec 2021 19:14:33 +0900 Subject: [PATCH 069/146] Fix autopilot not working as expected on touch devices Closes https://github.com/ppy/osu/issues/12731. I haven't tested this, but quite confident it should work. Will test later today unless someone else beats me. --- osu.Game.Rulesets.Osu/OsuInputManager.cs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/osu.Game.Rulesets.Osu/OsuInputManager.cs b/osu.Game.Rulesets.Osu/OsuInputManager.cs index 7314021a14..f5fc3de381 100644 --- a/osu.Game.Rulesets.Osu/OsuInputManager.cs +++ b/osu.Game.Rulesets.Osu/OsuInputManager.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.ComponentModel; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; +using osu.Framework.Input.StateChanges.Events; using osu.Game.Rulesets.UI; namespace osu.Game.Rulesets.Osu @@ -39,6 +40,17 @@ namespace osu.Game.Rulesets.Osu return base.Handle(e); } + protected override bool HandleMouseTouchStateChange(TouchStateChangeEvent e) + { + if (!AllowUserCursorMovement) + { + // Still allow for forwarding of the "touch" part, but block the positional data. + e = new TouchStateChangeEvent(e.State, e.Input, e.Touch, false, null); + } + + return base.HandleMouseTouchStateChange(e); + } + private class OsuKeyBindingContainer : RulesetKeyBindingContainer { public bool AllowUserPresses = true; From 6057037e355b06eb763609200c06adefa99e31c2 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 10 Dec 2021 20:08:59 +0900 Subject: [PATCH 070/146] Move playlist item beatmap population to MatchSubScreen --- .../StatefulMultiplayerClientTest.cs | 4 -- .../TestSceneAllPlayersQueueMode.cs | 16 ++--- .../Multiplayer/TestSceneHostOnlyQueueMode.cs | 10 +-- .../Multiplayer/TestSceneMultiplayer.cs | 2 +- .../Multiplayer/TestSceneMultiplayerPlayer.cs | 7 +- .../TestSceneMultiplayerQueueList.cs | 24 +++---- .../Online/Multiplayer/MultiplayerClient.cs | 65 +++++-------------- .../Multiplayer/OnlineMultiplayerClient.cs | 2 +- .../Screens/OnlinePlay/Match/RoomSubScreen.cs | 2 +- .../Match/MultiplayerReadyButton.cs | 10 ++- .../Multiplayer/MultiplayerMatchSongSelect.cs | 6 +- .../Multiplayer/MultiplayerMatchSubScreen.cs | 49 ++++++++++++-- .../Multiplayer/TestMultiplayerClient.cs | 2 +- 13 files changed, 103 insertions(+), 96 deletions(-) diff --git a/osu.Game.Tests/NonVisual/Multiplayer/StatefulMultiplayerClientTest.cs b/osu.Game.Tests/NonVisual/Multiplayer/StatefulMultiplayerClientTest.cs index 42305ccd81..bc0041e2c2 100644 --- a/osu.Game.Tests/NonVisual/Multiplayer/StatefulMultiplayerClientTest.cs +++ b/osu.Game.Tests/NonVisual/Multiplayer/StatefulMultiplayerClientTest.cs @@ -45,8 +45,6 @@ namespace osu.Game.Tests.NonVisual.Multiplayer AddRepeatStep("add some users", () => Client.AddUser(new APIUser { Id = id++ }), 5); checkPlayingUserCount(0); - AddAssert("playlist item is available", () => Client.CurrentMatchPlayingItem.Value != null); - changeState(3, MultiplayerUserState.WaitingForLoad); checkPlayingUserCount(3); @@ -64,8 +62,6 @@ namespace osu.Game.Tests.NonVisual.Multiplayer AddStep("leave room", () => Client.LeaveRoom()); checkPlayingUserCount(0); - - AddAssert("playlist item is null", () => Client.CurrentMatchPlayingItem.Value == null); } [Test] diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneAllPlayersQueueMode.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneAllPlayersQueueMode.cs index 8373979308..4bf38c9ff8 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneAllPlayersQueueMode.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneAllPlayersQueueMode.cs @@ -19,7 +19,7 @@ namespace osu.Game.Tests.Visual.Multiplayer [Test] public void TestFirstItemSelectedByDefault() { - AddAssert("first item selected", () => Client.CurrentMatchPlayingItem.Value?.ID == Client.APIRoom?.Playlist[0].ID); + AddAssert("first item selected", () => Client.Room?.Settings.PlaylistItemId == Client.APIRoom?.Playlist[0].ID); } [Test] @@ -27,13 +27,11 @@ namespace osu.Game.Tests.Visual.Multiplayer { addItem(() => OtherBeatmap); AddAssert("playlist has 2 items", () => Client.APIRoom?.Playlist.Count == 2); - AddAssert("last playlist item is different", () => Client.APIRoom?.Playlist[1].Beatmap.Value.OnlineID == OtherBeatmap.OnlineID); addItem(() => InitialBeatmap); AddAssert("playlist has 3 items", () => Client.APIRoom?.Playlist.Count == 3); - AddAssert("last playlist item is different", () => Client.APIRoom?.Playlist[2].Beatmap.Value.OnlineID == InitialBeatmap.OnlineID); - AddAssert("first item still selected", () => Client.CurrentMatchPlayingItem.Value?.ID == Client.APIRoom?.Playlist[0].ID); + AddAssert("first item still selected", () => Client.Room?.Settings.PlaylistItemId == Client.APIRoom?.Playlist[0].ID); } [Test] @@ -43,7 +41,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddAssert("playlist has only one item", () => Client.APIRoom?.Playlist.Count == 1); AddAssert("playlist item is expired", () => Client.APIRoom?.Playlist[0].Expired == true); - AddAssert("last item selected", () => Client.CurrentMatchPlayingItem.Value?.ID == Client.APIRoom?.Playlist[0].ID); + AddAssert("last item selected", () => Client.Room?.Settings.PlaylistItemId == Client.APIRoom?.Playlist[0].ID); } [Test] @@ -55,12 +53,12 @@ namespace osu.Game.Tests.Visual.Multiplayer RunGameplay(); AddAssert("first item expired", () => Client.APIRoom?.Playlist[0].Expired == true); - AddAssert("next item selected", () => Client.CurrentMatchPlayingItem.Value?.ID == Client.APIRoom?.Playlist[1].ID); + AddAssert("next item selected", () => Client.Room?.Settings.PlaylistItemId == Client.APIRoom?.Playlist[1].ID); RunGameplay(); AddAssert("second item expired", () => Client.APIRoom?.Playlist[1].Expired == true); - AddAssert("next item selected", () => Client.CurrentMatchPlayingItem.Value?.ID == Client.APIRoom?.Playlist[2].ID); + AddAssert("next item selected", () => Client.Room?.Settings.PlaylistItemId == Client.APIRoom?.Playlist[2].ID); } [Test] @@ -74,8 +72,8 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("change queue mode", () => Client.ChangeSettings(queueMode: QueueMode.HostOnly)); AddAssert("playlist has 3 items", () => Client.APIRoom?.Playlist.Count == 3); - AddAssert("playlist item is the other beatmap", () => Client.CurrentMatchPlayingItem.Value?.BeatmapID == OtherBeatmap.OnlineID); - AddAssert("playlist item is not expired", () => Client.APIRoom?.Playlist[1].Expired == false); + AddAssert("item 2 is not expired", () => Client.APIRoom?.Playlist[1].Expired == false); + AddAssert("current item is the other beatmap", () => Client.Room?.Settings.PlaylistItemId == 2); } [Test] diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneHostOnlyQueueMode.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneHostOnlyQueueMode.cs index ccac3de304..c06bd8fdbe 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneHostOnlyQueueMode.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneHostOnlyQueueMode.cs @@ -20,7 +20,7 @@ namespace osu.Game.Tests.Visual.Multiplayer [Test] public void TestFirstItemSelectedByDefault() { - AddAssert("first item selected", () => Client.CurrentMatchPlayingItem.Value?.ID == Client.APIRoom?.Playlist[0].ID); + AddAssert("first item selected", () => Client.Room?.Settings.PlaylistItemId == Client.APIRoom?.Playlist[0].ID); } [Test] @@ -28,7 +28,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { selectNewItem(() => InitialBeatmap); - AddAssert("playlist item still selected", () => Client.CurrentMatchPlayingItem.Value?.ID == Client.APIRoom?.Playlist[0].ID); + AddAssert("playlist item still selected", () => Client.Room?.Settings.PlaylistItemId == Client.APIRoom?.Playlist[0].ID); } [Test] @@ -36,7 +36,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { selectNewItem(() => OtherBeatmap); - AddAssert("playlist item still selected", () => Client.CurrentMatchPlayingItem.Value?.ID == Client.APIRoom?.Playlist[0].ID); + AddAssert("playlist item still selected", () => Client.Room?.Settings.PlaylistItemId == Client.APIRoom?.Playlist[0].ID); } [Test] @@ -47,7 +47,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddAssert("playlist contains two items", () => Client.APIRoom?.Playlist.Count == 2); AddAssert("first playlist item expired", () => Client.APIRoom?.Playlist[0].Expired == true); AddAssert("second playlist item not expired", () => Client.APIRoom?.Playlist[1].Expired == false); - AddAssert("second playlist item selected", () => Client.CurrentMatchPlayingItem.Value?.ID == Client.APIRoom?.Playlist[1].ID); + AddAssert("second playlist item selected", () => Client.Room?.Settings.PlaylistItemId == Client.APIRoom?.Playlist[1].ID); } [Test] @@ -97,7 +97,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("select other beatmap", () => ((Screens.Select.SongSelect)CurrentSubScreen).FinaliseSelection(otherBeatmap = beatmap())); AddUntilStep("wait for return to match", () => CurrentSubScreen is MultiplayerMatchSubScreen); - AddUntilStep("selected item is new beatmap", () => Client.CurrentMatchPlayingItem.Value?.Beatmap.Value?.OnlineID == otherBeatmap.OnlineID); + AddUntilStep("selected item is new beatmap", () => (CurrentSubScreen as MultiplayerMatchSubScreen)?.SelectedItem.Value.BeatmapID == otherBeatmap.OnlineID); } private void addItem(Func beatmap) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs index 5eb0abc830..f20ab914e2 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs @@ -416,7 +416,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("Enter song select", () => { var currentSubScreen = ((Screens.OnlinePlay.Multiplayer.Multiplayer)multiplayerScreenStack.CurrentScreen).CurrentSubScreen; - ((MultiplayerMatchSubScreen)currentSubScreen).OpenSongSelection(client.CurrentMatchPlayingItem.Value); + ((MultiplayerMatchSubScreen)currentSubScreen).OpenSongSelection(client.Room?.Settings.PlaylistItemId); }); AddUntilStep("wait for song select", () => this.ChildrenOfType().FirstOrDefault()?.BeatmapSetsLoaded == true); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlayer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlayer.cs index 5708b2f789..73f2ed5b39 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlayer.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlayer.cs @@ -6,6 +6,7 @@ using NUnit.Framework; using osu.Framework.Screens; using osu.Framework.Testing; using osu.Game.Online.Multiplayer; +using osu.Game.Online.Rooms; using osu.Game.Rulesets.Osu; using osu.Game.Screens.OnlinePlay.Multiplayer; @@ -27,7 +28,11 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("initialise gameplay", () => { - Stack.Push(player = new MultiplayerPlayer(Client.APIRoom, Client.CurrentMatchPlayingItem.Value, Client.Room?.Users.ToArray())); + Stack.Push(player = new MultiplayerPlayer(Client.APIRoom, new PlaylistItem + { + Beatmap = { Value = Beatmap.Value.BeatmapInfo }, + Ruleset = { Value = Beatmap.Value.BeatmapInfo.Ruleset } + }, Client.Room?.Users.ToArray())); }); AddUntilStep("wait for player to be current", () => player.IsCurrentScreen() && player.IsLoaded); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerQueueList.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerQueueList.cs index a2b2da0aec..61a92c32a4 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerQueueList.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerQueueList.cs @@ -6,6 +6,7 @@ using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Audio; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Platform; @@ -27,11 +28,12 @@ namespace osu.Game.Tests.Visual.Multiplayer { public class TestSceneMultiplayerQueueList : MultiplayerTestScene { - private MultiplayerQueueList playlist; + private readonly Bindable selectedItem = new Bindable(); [Cached(typeof(UserLookupCache))] private readonly TestUserLookupCache userLookupCache = new TestUserLookupCache(); + private MultiplayerQueueList playlist; private BeatmapManager beatmaps; private RulesetStore rulesets; private BeatmapSetInfo importedSet; @@ -50,12 +52,14 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("create playlist", () => { + selectedItem.Value = null; + Child = playlist = new MultiplayerQueueList { Anchor = Anchor.Centre, Origin = Anchor.Centre, Size = new Vector2(500, 300), - SelectedItem = { BindTarget = Client.CurrentMatchPlayingItem }, + SelectedItem = { BindTarget = selectedItem }, Items = { BindTarget = Client.APIRoom!.Playlist } }; }); @@ -107,22 +111,14 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("set all players queue mode", () => Client.ChangeSettings(new MultiplayerRoomSettings { QueueMode = QueueMode.AllPlayers })); AddUntilStep("wait for queue mode change", () => Client.APIRoom?.QueueMode.Value == QueueMode.AllPlayers); - assertDeleteButtonVisibility(0, false); - addPlaylistItem(() => API.LocalUser.Value.OnlineID); + + AddStep("select item 0", () => selectedItem.Value = playlist.ChildrenOfType>().ElementAt(0).Model); assertDeleteButtonVisibility(0, false); assertDeleteButtonVisibility(1, true); - // Run through gameplay. - AddStep("set state to ready", () => Client.ChangeUserState(API.LocalUser.Value.Id, MultiplayerUserState.Ready)); - AddUntilStep("local state is ready", () => Client.LocalUser?.State == MultiplayerUserState.Ready); - AddStep("start match", () => Client.StartMatch()); - AddUntilStep("match started", () => Client.LocalUser?.State == MultiplayerUserState.WaitingForLoad); - AddStep("set state to loaded", () => Client.ChangeUserState(API.LocalUser.Value.Id, MultiplayerUserState.Loaded)); - AddUntilStep("local state is playing", () => Client.LocalUser?.State == MultiplayerUserState.Playing); - AddStep("set state to finished play", () => Client.ChangeUserState(API.LocalUser.Value.Id, MultiplayerUserState.FinishedPlay)); - AddUntilStep("local state is results", () => Client.LocalUser?.State == MultiplayerUserState.Results); - + AddStep("select item 1", () => selectedItem.Value = playlist.ChildrenOfType>().ElementAt(1).Model); + assertDeleteButtonVisibility(0, true); assertDeleteButtonVisibility(1, false); } diff --git a/osu.Game/Online/Multiplayer/MultiplayerClient.cs b/osu.Game/Online/Multiplayer/MultiplayerClient.cs index 55b4def908..f366de557f 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerClient.cs @@ -95,8 +95,6 @@ namespace osu.Game.Online.Multiplayer protected readonly BindableList PlayingUserIds = new BindableList(); - public readonly Bindable CurrentMatchPlayingItem = new Bindable(); - /// /// The corresponding to the local player, if available. /// @@ -162,9 +160,6 @@ namespace osu.Game.Online.Multiplayer var joinedRoom = await JoinRoom(room.RoomID.Value.Value, password ?? room.Password.Value).ConfigureAwait(false); Debug.Assert(joinedRoom != null); - // Populate playlist items. - var playlistItems = await Task.WhenAll(joinedRoom.Playlist.Select(item => createPlaylistItem(item, item.ID == joinedRoom.Settings.PlaylistItemId))).ConfigureAwait(false); - // Populate users. Debug.Assert(joinedRoom.Users != null); await Task.WhenAll(joinedRoom.Users.Select(PopulateUser)).ConfigureAwait(false); @@ -176,7 +171,7 @@ namespace osu.Game.Online.Multiplayer APIRoom = room; APIRoom.Playlist.Clear(); - APIRoom.Playlist.AddRange(playlistItems); + APIRoom.Playlist.AddRange(joinedRoom.Playlist.Select(createPlaylistItem)); Debug.Assert(LocalUser != null); addUserToAPIRoom(LocalUser); @@ -219,7 +214,6 @@ namespace osu.Game.Online.Multiplayer { APIRoom = null; Room = null; - CurrentMatchPlayingItem.Value = null; PlayingUserIds.Clear(); RoomUpdated?.Invoke(); @@ -477,28 +471,7 @@ namespace osu.Game.Online.Multiplayer Debug.Assert(APIRoom != null); Debug.Assert(Room != null); - Scheduler.Add(() => - { - // ensure the new selected item is populated immediately. - var playlistItem = APIRoom.Playlist.Single(p => p.ID == newSettings.PlaylistItemId); - - if (playlistItem != null) - { - GetAPIBeatmap(playlistItem.BeatmapID).ContinueWith(b => - { - // Should be called outside of the `Scheduler` logic (and specifically accessing `Exception`) to suppress an exception from firing outwards. - bool success = b.Exception == null; - - Scheduler.Add(() => - { - if (success) - playlistItem.Beatmap.Value = b.Result; - - updateLocalRoomSettings(newSettings); - }); - }); - } - }); + Scheduler.Add(() => updateLocalRoomSettings(newSettings)); return Task.CompletedTask; } @@ -653,12 +626,10 @@ namespace osu.Game.Online.Multiplayer return Task.CompletedTask; } - public async Task PlaylistItemAdded(MultiplayerPlaylistItem item) + public Task PlaylistItemAdded(MultiplayerPlaylistItem item) { if (Room == null) - return; - - var playlistItem = await createPlaylistItem(item, true).ConfigureAwait(false); + return Task.CompletedTask; Scheduler.Add(() => { @@ -668,11 +639,13 @@ namespace osu.Game.Online.Multiplayer Debug.Assert(APIRoom != null); Room.Playlist.Add(item); - APIRoom.Playlist.Add(playlistItem); + APIRoom.Playlist.Add(createPlaylistItem(item)); ItemAdded?.Invoke(item); RoomUpdated?.Invoke(); }); + + return Task.CompletedTask; } public Task PlaylistItemRemoved(long playlistItemId) @@ -697,12 +670,10 @@ namespace osu.Game.Online.Multiplayer return Task.CompletedTask; } - public async Task PlaylistItemChanged(MultiplayerPlaylistItem item) + public Task PlaylistItemChanged(MultiplayerPlaylistItem item) { if (Room == null) - return; - - var playlistItem = await createPlaylistItem(item, true).ConfigureAwait(false); + return Task.CompletedTask; Scheduler.Add(() => { @@ -715,15 +686,13 @@ namespace osu.Game.Online.Multiplayer int existingIndex = APIRoom.Playlist.IndexOf(APIRoom.Playlist.Single(existing => existing.ID == item.ID)); APIRoom.Playlist.RemoveAt(existingIndex); - APIRoom.Playlist.Insert(existingIndex, playlistItem); - - // If the currently-selected item was the one that got replaced, update the selected item to the new one. - if (CurrentMatchPlayingItem.Value?.ID == playlistItem.ID) - CurrentMatchPlayingItem.Value = playlistItem; + APIRoom.Playlist.Insert(existingIndex, createPlaylistItem(item)); ItemChanged?.Invoke(item); RoomUpdated?.Invoke(); }); + + return Task.CompletedTask; } /// @@ -752,12 +721,11 @@ namespace osu.Game.Online.Multiplayer APIRoom.Password.Value = Room.Settings.Password; APIRoom.Type.Value = Room.Settings.MatchType; APIRoom.QueueMode.Value = Room.Settings.QueueMode; - RoomUpdated?.Invoke(); - CurrentMatchPlayingItem.Value = APIRoom.Playlist.SingleOrDefault(p => p.ID == settings.PlaylistItemId); + RoomUpdated?.Invoke(); } - private async Task createPlaylistItem(MultiplayerPlaylistItem item, bool populateBeatmapImmediately) + private PlaylistItem createPlaylistItem(MultiplayerPlaylistItem item) { var ruleset = Rulesets.GetRuleset(item.RulesetID); @@ -779,9 +747,6 @@ namespace osu.Game.Online.Multiplayer playlistItem.RequiredMods.AddRange(item.RequiredMods.Select(m => m.ToMod(rulesetInstance))); playlistItem.AllowedMods.AddRange(item.AllowedMods.Select(m => m.ToMod(rulesetInstance))); - if (populateBeatmapImmediately) - playlistItem.Beatmap.Value = await GetAPIBeatmap(item.BeatmapID).ConfigureAwait(false); - return playlistItem; } @@ -791,7 +756,7 @@ namespace osu.Game.Online.Multiplayer /// The beatmap ID. /// A token to cancel the request. /// The retrieval task. - protected abstract Task GetAPIBeatmap(int beatmapId, CancellationToken cancellationToken = default); + public abstract Task GetAPIBeatmap(int beatmapId, CancellationToken cancellationToken = default); /// /// For the provided user ID, update whether the user is included in . diff --git a/osu.Game/Online/Multiplayer/OnlineMultiplayerClient.cs b/osu.Game/Online/Multiplayer/OnlineMultiplayerClient.cs index d268d2bf69..f911ef3121 100644 --- a/osu.Game/Online/Multiplayer/OnlineMultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/OnlineMultiplayerClient.cs @@ -178,7 +178,7 @@ namespace osu.Game.Online.Multiplayer return connection.InvokeAsync(nameof(IMultiplayerServer.RemovePlaylistItem), playlistItemId); } - protected override Task GetAPIBeatmap(int beatmapId, CancellationToken cancellationToken = default) + public override Task GetAPIBeatmap(int beatmapId, CancellationToken cancellationToken = default) { return beatmapLookupCache.GetBeatmapAsync(beatmapId, cancellationToken); } diff --git a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs index 184ac2c563..55f5622afd 100644 --- a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs @@ -28,7 +28,7 @@ namespace osu.Game.Screens.OnlinePlay.Match public abstract class RoomSubScreen : OnlinePlaySubScreen, IPreviewTrackOwner { [Cached(typeof(IBindable))] - protected readonly Bindable SelectedItem = new Bindable(); + public readonly Bindable SelectedItem = new Bindable(); public override bool? AllowTrackAdjustments => true; diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerReadyButton.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerReadyButton.cs index 874113d859..06959d942f 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerReadyButton.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerReadyButton.cs @@ -63,6 +63,13 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match sampleUnready = audio.Samples.Get(@"Multiplayer/player-unready"); } + protected override void LoadComplete() + { + base.LoadComplete(); + + SelectedItem.BindValueChanged(_ => updateState()); + } + protected override void OnRoomUpdated() { base.OnRoomUpdated(); @@ -104,7 +111,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match bool enableButton = Room?.State == MultiplayerRoomState.Open - && Client.CurrentMatchPlayingItem.Value?.Expired == false + && SelectedItem.Value?.ID == Room.Settings.PlaylistItemId + && !Room.Playlist.Single(i => i.ID == Room.Settings.PlaylistItemId).Expired && !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. diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs index 8d3686dd6d..073497e1ce 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs @@ -25,7 +25,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer [Resolved] private MultiplayerClient client { get; set; } - private readonly PlaylistItem itemToEdit; + private readonly long? itemToEdit; private LoadingLayer loadingLayer; @@ -36,7 +36,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer /// The item to be edited. May be null, in which case a new item will be added to the playlist. /// An optional initial beatmap selection to perform. /// An optional initial ruleset selection to perform. - public MultiplayerMatchSongSelect(Room room, PlaylistItem itemToEdit = null, WorkingBeatmap beatmap = null, RulesetInfo ruleset = null) + public MultiplayerMatchSongSelect(Room room, long? itemToEdit = null, WorkingBeatmap beatmap = null, RulesetInfo ruleset = null) : base(room) { this.itemToEdit = itemToEdit; @@ -67,7 +67,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer var multiplayerItem = new MultiplayerPlaylistItem { - ID = itemToEdit?.ID ?? 0, + ID = itemToEdit ?? 0, BeatmapID = item.BeatmapID, BeatmapChecksum = item.Beatmap.Value.MD5Hash, RulesetID = item.RulesetID, diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs index 946c749db3..3543836d8d 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; +using System.Threading.Tasks; using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -67,8 +68,6 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer { base.LoadComplete(); - SelectedItem.BindTo(client.CurrentMatchPlayingItem); - BeatmapAvailability.BindValueChanged(updateBeatmapAvailability, true); UserMods.BindValueChanged(onUserModsChanged); @@ -147,7 +146,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer new MultiplayerPlaylist { RelativeSizeAxes = Axes.Both, - RequestEdit = OpenSongSelection + RequestEdit = item => OpenSongSelection(item.ID) } }, new[] @@ -224,7 +223,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer /// Opens the song selection screen to add or edit an item. /// /// An optional playlist item to edit. If null, a new item will be added instead. - internal void OpenSongSelection([CanBeNull] PlaylistItem itemToEdit = null) + internal void OpenSongSelection(long? itemToEdit = null) { if (!this.IsCurrentScreen()) return; @@ -389,11 +388,48 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer return; } + updateCurrentItem(); + addItemButton.Alpha = client.IsHost || Room.QueueMode.Value != QueueMode.HostOnly ? 1 : 0; Scheduler.AddOnce(UpdateMods); } + private void updateCurrentItem() + { + Debug.Assert(client.Room != null); + + var expectedSelectedItem = Room.Playlist.SingleOrDefault(i => i.ID == client.Room.Settings.PlaylistItemId); + + if (expectedSelectedItem == null) + return; + + if (SelectedItem.Value?.Equals(expectedSelectedItem) == true && expectedSelectedItem.Beatmap.Value != null) + return; + + SelectedItem.Value = null; + + if (expectedSelectedItem.Beatmap.Value == null) + { + Task.Run(async () => + { + var beatmap = await client.GetAPIBeatmap(expectedSelectedItem.BeatmapID).ConfigureAwait(false); + + Schedule(() => + { + expectedSelectedItem.Beatmap.Value = beatmap; + + if (Room.Playlist.SingleOrDefault(i => i.ID == client.Room?.Settings.PlaylistItemId)?.Equals(expectedSelectedItem) == true) + applyCurrentItem(); + }); + }); + } + else + applyCurrentItem(); + + void applyCurrentItem() => SelectedItem.Value = expectedSelectedItem; + } + private void handleRoomLost() => Schedule(() => { if (this.IsCurrentScreen()) @@ -446,6 +482,9 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer if (!this.IsCurrentScreen()) return; + if (client.Room == null) + return; + if (!client.IsHost) { // todo: should handle this when the request queue is implemented. @@ -454,7 +493,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer return; } - this.Push(new MultiplayerMatchSongSelect(Room, SelectedItem.Value, beatmap, ruleset)); + this.Push(new MultiplayerMatchSongSelect(Room, client.Room.Settings.PlaylistItemId, beatmap, ruleset)); } protected override void Dispose(bool isDisposing) diff --git a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs index d22f0415e6..d20d6b1d37 100644 --- a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs +++ b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs @@ -376,7 +376,7 @@ namespace osu.Game.Tests.Visual.Multiplayer public override Task RemovePlaylistItem(long playlistItemId) => RemoveUserPlaylistItem(api.LocalUser.Value.OnlineID, playlistItemId); - protected override Task GetAPIBeatmap(int beatmapId, CancellationToken cancellationToken = default) + public override Task GetAPIBeatmap(int beatmapId, CancellationToken cancellationToken = default) { IBeatmapSetInfo? set = roomManager.ServerSideRooms.SelectMany(r => r.Playlist) .FirstOrDefault(p => p.BeatmapID == beatmapId)?.Beatmap.Value.BeatmapSet From bc1f1f35b5a73dde39bfaab211300df330f5f7f3 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 10 Dec 2021 16:40:51 +0300 Subject: [PATCH 071/146] Remove now redundant inclusion of `TouchMoveEvent` in `OsuInputManager.Handle` Now it's handled separately via the `HandleMouseTouchStateChange` override. --- osu.Game.Rulesets.Osu/OsuInputManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/OsuInputManager.cs b/osu.Game.Rulesets.Osu/OsuInputManager.cs index f5fc3de381..57704b3bd8 100644 --- a/osu.Game.Rulesets.Osu/OsuInputManager.cs +++ b/osu.Game.Rulesets.Osu/OsuInputManager.cs @@ -35,7 +35,7 @@ namespace osu.Game.Rulesets.Osu protected override bool Handle(UIEvent e) { - if ((e is MouseMoveEvent || e is TouchMoveEvent) && !AllowUserCursorMovement) return false; + if (e is MouseMoveEvent && !AllowUserCursorMovement) return false; return base.Handle(e); } From cf3041128888b4be5993728e8604c90df1cdd49b Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 10 Dec 2021 17:13:13 +0300 Subject: [PATCH 072/146] Revert "Remove now redundant inclusion of `TouchMoveEvent` in `OsuInputManager.Handle`" This reverts commit bc1f1f35b5a73dde39bfaab211300df330f5f7f3. --- osu.Game.Rulesets.Osu/OsuInputManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/OsuInputManager.cs b/osu.Game.Rulesets.Osu/OsuInputManager.cs index 57704b3bd8..f5fc3de381 100644 --- a/osu.Game.Rulesets.Osu/OsuInputManager.cs +++ b/osu.Game.Rulesets.Osu/OsuInputManager.cs @@ -35,7 +35,7 @@ namespace osu.Game.Rulesets.Osu protected override bool Handle(UIEvent e) { - if (e is MouseMoveEvent && !AllowUserCursorMovement) return false; + if ((e is MouseMoveEvent || e is TouchMoveEvent) && !AllowUserCursorMovement) return false; return base.Handle(e); } From 63a017bc8e43bbdc61db396047e100eaf9814d80 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Sat, 11 Dec 2021 19:33:37 +0900 Subject: [PATCH 073/146] Use Array.Empty instead --- osu.Game/Screens/Edit/EditorLoader.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/EditorLoader.cs b/osu.Game/Screens/Edit/EditorLoader.cs index 27ae8ae497..15d70e28b6 100644 --- a/osu.Game/Screens/Edit/EditorLoader.cs +++ b/osu.Game/Screens/Edit/EditorLoader.cs @@ -60,7 +60,7 @@ namespace osu.Game.Screens.Edit base.LoadComplete(); // will be restored via lease, see `DisallowExternalBeatmapRulesetChanges`. - Mods.Value = ArraySegment.Empty; + Mods.Value = Array.Empty(); } protected virtual Editor CreateEditor() => new Editor(this); From 25b274c323737c73de6d45d55a57096eec2faca8 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Sat, 11 Dec 2021 22:47:08 +0900 Subject: [PATCH 074/146] Fix starting gameplay too early after import --- .../Multiplayer/TestSceneMultiplayer.cs | 40 +++++++++++-------- .../Screens/OnlinePlay/Match/RoomSubScreen.cs | 8 ++-- .../Multiplayer/MultiplayerMatchSubScreen.cs | 21 ++++++++-- 3 files changed, 46 insertions(+), 23 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs index f20ab914e2..bc2902480d 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs @@ -391,9 +391,9 @@ namespace osu.Game.Tests.Visual.Multiplayer } }); - AddStep("set user ready", () => client.ChangeState(MultiplayerUserState.Ready)); - AddStep("delete beatmap", () => beatmaps.Delete(importedSet)); + pressReadyButton(); + AddStep("delete beatmap", () => beatmaps.Delete(importedSet)); AddUntilStep("user state is idle", () => client.LocalUser?.State == MultiplayerUserState.Idle); } @@ -413,6 +413,8 @@ namespace osu.Game.Tests.Visual.Multiplayer } }); + pressReadyButton(); + AddStep("Enter song select", () => { var currentSubScreen = ((Screens.OnlinePlay.Multiplayer.Multiplayer)multiplayerScreenStack.CurrentScreen).CurrentSubScreen; @@ -592,19 +594,8 @@ namespace osu.Game.Tests.Visual.Multiplayer } }); - AddUntilStep("wait for ready button to be enabled", () => readyButton.ChildrenOfType().Single().Enabled.Value); - - AddStep("click ready button", () => - { - InputManager.MoveMouseTo(readyButton); - InputManager.Click(MouseButton.Left); - }); - - AddUntilStep("wait for player to be ready", () => client.Room?.Users[0].State == MultiplayerUserState.Ready); - AddUntilStep("wait for ready button to be enabled", () => readyButton.ChildrenOfType().Single().Enabled.Value); - - AddStep("click start button", () => InputManager.Click(MouseButton.Left)); - + pressReadyButton(); + pressReadyButton(); AddUntilStep("wait for player", () => multiplayerScreenStack.CurrentScreen is Player); // Gameplay runs in real-time, so we need to incrementally check if gameplay has finished in order to not time out. @@ -665,7 +656,24 @@ namespace osu.Game.Tests.Visual.Multiplayer }); } - private MultiplayerReadyButton readyButton => this.ChildrenOfType().Single(); + private ReadyButton readyButton => this.ChildrenOfType().Single(); + + private void pressReadyButton() + { + AddUntilStep("wait for ready button to be enabled", () => readyButton.Enabled.Value); + + MultiplayerUserState lastState = MultiplayerUserState.Idle; + + AddStep("click ready button", () => + { + lastState = client.LocalUser?.State ?? MultiplayerUserState.Idle; + + InputManager.MoveMouseTo(readyButton); + InputManager.Click(MouseButton.Left); + }); + + AddUntilStep("wait for state change", () => client.LocalUser?.State != lastState); + } private void createRoom(Func room) { diff --git a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs index 55f5622afd..e5a6915ca3 100644 --- a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs @@ -302,7 +302,7 @@ namespace osu.Game.Screens.OnlinePlay.Match public override void OnResuming(IScreen last) { base.OnResuming(last); - updateWorkingBeatmap(); + UpdateWorkingBeatmap(); beginHandlingTrack(); Scheduler.AddOnce(UpdateMods); } @@ -345,7 +345,7 @@ namespace osu.Game.Screens.OnlinePlay.Match private void selectedItemChanged() { - updateWorkingBeatmap(); + UpdateWorkingBeatmap(); var selected = SelectedItem.Value; @@ -374,9 +374,9 @@ namespace osu.Game.Screens.OnlinePlay.Match } } - private void beatmapUpdated(BeatmapSetInfo set) => Schedule(updateWorkingBeatmap); + private void beatmapUpdated(BeatmapSetInfo set) => Schedule(UpdateWorkingBeatmap); - private void updateWorkingBeatmap() + protected virtual void UpdateWorkingBeatmap() { var beatmap = SelectedItem.Value?.Beatmap.Value; diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs index 3543836d8d..b517142069 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs @@ -15,6 +15,7 @@ using osu.Framework.Screens; using osu.Framework.Threading; using osu.Game.Beatmaps; using osu.Game.Configuration; +using osu.Game.Extensions; using osu.Game.Online; using osu.Game.Online.Multiplayer; using osu.Game.Online.Rooms; @@ -326,10 +327,24 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer if (client.LocalUser?.State == MultiplayerUserState.Ready) client.ChangeState(MultiplayerUserState.Idle); } - else + } + + protected override void UpdateWorkingBeatmap() + { + var lastBeatmap = Beatmap.Value; + + base.UpdateWorkingBeatmap(); + + if (Beatmap.Value.BeatmapInfo.MatchesOnlineID(lastBeatmap.BeatmapInfo)) + return; + + if (!Beatmap.Value.BeatmapInfo.MatchesOnlineID(SelectedItem.Value?.Beatmap.Value)) + return; + + if (client.LocalUser?.State == MultiplayerUserState.Spectating + && (client.Room?.State == MultiplayerRoomState.WaitingForLoad || client.Room?.State == MultiplayerRoomState.Playing)) { - if (client.LocalUser?.State == MultiplayerUserState.Spectating && (client.Room?.State == MultiplayerRoomState.WaitingForLoad || client.Room?.State == MultiplayerRoomState.Playing)) - onLoadRequested(); + onLoadRequested(); } } From ece2cddb7fd11171450b336bcf049efff3b28d38 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Sat, 11 Dec 2021 22:51:20 +0900 Subject: [PATCH 075/146] Fix DrawableRoomPlaylistItem lookup interfering with tests --- .../Screens/OnlinePlay/DrawableRoomPlaylistItem.cs | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs index 8042f7d772..a877c6aca9 100644 --- a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs +++ b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -24,6 +25,7 @@ using osu.Game.Graphics.Containers; using osu.Game.Graphics.UserInterface; using osu.Game.Online; using osu.Game.Online.Chat; +using osu.Game.Online.Multiplayer; using osu.Game.Online.Rooms; using osu.Game.Overlays.BeatmapSet; using osu.Game.Rulesets; @@ -90,6 +92,10 @@ namespace osu.Game.Screens.OnlinePlay [Resolved] private UserLookupCache userLookupCache { get; set; } + [CanBeNull] + [Resolved(CanBeNull = true)] + private MultiplayerClient multiplayerClient { get; set; } + [Resolved] private BeatmapLookupCache beatmapLookupCache { get; set; } @@ -157,7 +163,13 @@ namespace osu.Game.Screens.OnlinePlay if (Item.Beatmap.Value == null) { - var foundBeatmap = await beatmapLookupCache.GetBeatmapAsync(Item.BeatmapID).ConfigureAwait(false); + IBeatmapInfo foundBeatmap; + + if (multiplayerClient != null) + foundBeatmap = await multiplayerClient.GetAPIBeatmap(Item.BeatmapID).ConfigureAwait(false); + else + foundBeatmap = await beatmapLookupCache.GetBeatmapAsync(Item.BeatmapID).ConfigureAwait(false); + Schedule(() => Item.Beatmap.Value = foundBeatmap); } } From 99cd36d2f611eea89ae74591b6ba37fb16cac9f9 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Sat, 11 Dec 2021 22:52:06 +0900 Subject: [PATCH 076/146] Resolve some test failures due to async population --- .../Visual/Multiplayer/TestSceneAllPlayersQueueMode.cs | 2 +- osu.Game.Tests/Visual/Multiplayer/TestSceneHostOnlyQueueMode.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneAllPlayersQueueMode.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneAllPlayersQueueMode.cs index 4bf38c9ff8..a5744f9986 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneAllPlayersQueueMode.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneAllPlayersQueueMode.cs @@ -80,7 +80,7 @@ namespace osu.Game.Tests.Visual.Multiplayer public void TestCorrectItemSelectedAfterNewItemAdded() { addItem(() => OtherBeatmap); - AddAssert("selected beatmap is initial beatmap", () => Beatmap.Value.BeatmapInfo.OnlineID == InitialBeatmap.OnlineID); + AddUntilStep("selected beatmap is initial beatmap", () => Beatmap.Value.BeatmapInfo.OnlineID == InitialBeatmap.OnlineID); } private void addItem(Func beatmap) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneHostOnlyQueueMode.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneHostOnlyQueueMode.cs index c06bd8fdbe..ac1efdda4a 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneHostOnlyQueueMode.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneHostOnlyQueueMode.cs @@ -97,7 +97,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("select other beatmap", () => ((Screens.Select.SongSelect)CurrentSubScreen).FinaliseSelection(otherBeatmap = beatmap())); AddUntilStep("wait for return to match", () => CurrentSubScreen is MultiplayerMatchSubScreen); - AddUntilStep("selected item is new beatmap", () => (CurrentSubScreen as MultiplayerMatchSubScreen)?.SelectedItem.Value.BeatmapID == otherBeatmap.OnlineID); + AddUntilStep("selected item is new beatmap", () => (CurrentSubScreen as MultiplayerMatchSubScreen)?.SelectedItem.Value?.BeatmapID == otherBeatmap.OnlineID); } private void addItem(Func beatmap) From d6c08fae48dccfd80d2bd003f3b8119130a171ca Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Sat, 11 Dec 2021 23:08:17 +0900 Subject: [PATCH 077/146] Fix incorrect global beatmap with availability changes --- osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs | 7 +++++++ .../OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs | 2 ++ 2 files changed, 9 insertions(+) diff --git a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs index e5a6915ca3..6b395058fa 100644 --- a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs @@ -15,6 +15,7 @@ using osu.Framework.Graphics.Shapes; using osu.Framework.Screens; using osu.Game.Audio; using osu.Game.Beatmaps; +using osu.Game.Online; using osu.Game.Online.Rooms; using osu.Game.Overlays; using osu.Game.Overlays.Mods; @@ -378,6 +379,12 @@ namespace osu.Game.Screens.OnlinePlay.Match protected virtual void UpdateWorkingBeatmap() { + if (BeatmapAvailability.Value.State != DownloadState.LocallyAvailable) + { + Beatmap.Value = beatmapManager.GetWorkingBeatmap(null); + return; + } + var beatmap = SelectedItem.Value?.Beatmap.Value; // Retrieve the corresponding local beatmap, since we can't directly use the playlist's beatmap info diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs index b517142069..65190101b8 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs @@ -327,6 +327,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer if (client.LocalUser?.State == MultiplayerUserState.Ready) client.ChangeState(MultiplayerUserState.Idle); } + + UpdateWorkingBeatmap(); } protected override void UpdateWorkingBeatmap() From 9f792fec49366e8ac3cc9d31a4f8c0bc214d50c6 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Sun, 12 Dec 2021 16:11:48 +0900 Subject: [PATCH 078/146] Fix test failures from async item loading --- .../Visual/Multiplayer/TestSceneHostOnlyQueueMode.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneHostOnlyQueueMode.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneHostOnlyQueueMode.cs index ac1efdda4a..c7eeff81fe 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneHostOnlyQueueMode.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneHostOnlyQueueMode.cs @@ -9,6 +9,7 @@ using osu.Game.Beatmaps; using osu.Game.Online.Multiplayer; using osu.Game.Screens.OnlinePlay; using osu.Game.Screens.OnlinePlay.Multiplayer; +using osu.Game.Screens.OnlinePlay.Multiplayer.Match.Playlist; using osuTK.Input; namespace osu.Game.Tests.Visual.Multiplayer @@ -85,6 +86,12 @@ namespace osu.Game.Tests.Visual.Multiplayer private void selectNewItem(Func beatmap) { + AddUntilStep("wait for playlist panels to load", () => + { + var queueList = this.ChildrenOfType().Single(); + return queueList.ChildrenOfType().Count() == queueList.Items.Count; + }); + AddStep("click edit button", () => { InputManager.MoveMouseTo(this.ChildrenOfType().First()); From a6e77f172d30e4f4f0126f20f0ad95197549906a Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Sun, 12 Dec 2021 16:30:37 +0900 Subject: [PATCH 079/146] Add some comments --- .../OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs index 65190101b8..4fa21cb847 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs @@ -337,9 +337,12 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer base.UpdateWorkingBeatmap(); + // Nothing to do if the beatmap hasn't changed. if (Beatmap.Value.BeatmapInfo.MatchesOnlineID(lastBeatmap.BeatmapInfo)) return; + // The selected item is nulled during the beatmap query. During this, the working beatmap will be the dummy beatmap. + // We don't want to enter spectate mode with the dummy beatmap. if (!Beatmap.Value.BeatmapInfo.MatchesOnlineID(SelectedItem.Value?.Beatmap.Value)) return; @@ -421,9 +424,11 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer if (expectedSelectedItem == null) return; + // There's no reason to renew the selected item if its content hasn't changed. if (SelectedItem.Value?.Equals(expectedSelectedItem) == true && expectedSelectedItem.Beatmap.Value != null) return; + // Clear the selected item while the lookup is performed, so components like the ready button can enter their disabled states. SelectedItem.Value = null; if (expectedSelectedItem.Beatmap.Value == null) From 2cd2b10ce1b1fd705a5d63fe007aba821745a5d4 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Mon, 13 Dec 2021 06:54:57 +0900 Subject: [PATCH 080/146] Fix results sometimes showing incorrect score position --- .../TestScenePlaylistsResultsScreen.cs | 5 ++++- .../Playlists/PlaylistsResultsScreen.cs | 7 +++++++ .../Contracted/ContractedPanelTopContent.cs | 20 +++++++++++------- osu.Game/Screens/Ranking/ScorePanel.cs | 21 +++++++++++++++---- 4 files changed, 41 insertions(+), 12 deletions(-) diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs index 4ac3b7c733..64fc4797a0 100644 --- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs +++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs @@ -28,6 +28,7 @@ namespace osu.Game.Tests.Visual.Playlists public class TestScenePlaylistsResultsScreen : ScreenTestScene { private const int scores_per_result = 10; + private const int real_user_position = 200; private TestResultsScreen resultsScreen; @@ -58,6 +59,8 @@ namespace osu.Game.Tests.Visual.Playlists createResults(() => userScore); AddAssert("user score selected", () => this.ChildrenOfType().Single(p => p.Score.OnlineID == userScore.OnlineID).State == PanelState.Expanded); + AddAssert($"score panel position is {real_user_position}", + () => this.ChildrenOfType().Single(p => p.Score.OnlineID == userScore.OnlineID).ScorePosition.Value == real_user_position); } [Test] @@ -236,7 +239,7 @@ namespace osu.Game.Tests.Visual.Playlists EndedAt = userScore.Date, Passed = userScore.Passed, Rank = userScore.Rank, - Position = 200, + Position = real_user_position, MaxCombo = userScore.MaxCombo, TotalScore = userScore.TotalScore, User = userScore.User, diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsResultsScreen.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsResultsScreen.cs index 67727ef784..1e6722d51e 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsResultsScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsResultsScreen.cs @@ -87,6 +87,13 @@ namespace osu.Game.Screens.OnlinePlay.Playlists { var allScores = new List { userScore }; + // Other scores could have arrived between score submission and entering the results screen. Ensure the local player score position is up to date. + if (Score != null) + { + Score.Position = userScore.Position; + ScorePanelList.GetPanelForScore(Score).ScorePosition.Value = userScore.Position; + } + if (userScore.ScoresAround?.Higher != null) { allScores.AddRange(userScore.ScoresAround.Higher.Scores); diff --git a/osu.Game/Screens/Ranking/Contracted/ContractedPanelTopContent.cs b/osu.Game/Screens/Ranking/Contracted/ContractedPanelTopContent.cs index 0935ee7fb2..beff509dc6 100644 --- a/osu.Game/Screens/Ranking/Contracted/ContractedPanelTopContent.cs +++ b/osu.Game/Screens/Ranking/Contracted/ContractedPanelTopContent.cs @@ -2,36 +2,42 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; -using osu.Game.Scoring; namespace osu.Game.Screens.Ranking.Contracted { public class ContractedPanelTopContent : CompositeDrawable { - private readonly ScoreInfo score; + public readonly Bindable ScorePosition = new Bindable(); - public ContractedPanelTopContent(ScoreInfo score) + private OsuSpriteText text; + + public ContractedPanelTopContent() { - this.score = score; - RelativeSizeAxes = Axes.Both; } [BackgroundDependencyLoader] private void load() { - InternalChild = new OsuSpriteText + InternalChild = text = new OsuSpriteText { Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, Y = 6, - Text = score.Position != null ? $"#{score.Position}" : string.Empty, Font = OsuFont.GetFont(size: 18, weight: FontWeight.Bold) }; } + + protected override void LoadComplete() + { + base.LoadComplete(); + + ScorePosition.BindValueChanged(pos => text.Text = pos.NewValue != null ? $"#{pos.NewValue}" : string.Empty, true); + } } } diff --git a/osu.Game/Screens/Ranking/ScorePanel.cs b/osu.Game/Screens/Ranking/ScorePanel.cs index 6ddecf8297..bc6eb9e366 100644 --- a/osu.Game/Screens/Ranking/ScorePanel.cs +++ b/osu.Game/Screens/Ranking/ScorePanel.cs @@ -4,6 +4,7 @@ using System; using osu.Framework; using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; @@ -78,6 +79,11 @@ namespace osu.Game.Screens.Ranking public event Action StateChanged; + /// + /// The position of the score in the rankings. + /// + public readonly Bindable ScorePosition = new Bindable(); + /// /// An action to be invoked if this is clicked while in an expanded state. /// @@ -103,6 +109,8 @@ namespace osu.Game.Screens.Ranking { Score = score; displayWithFlair = isNewLocalScore; + + ScorePosition.Value = score.Position; } [BackgroundDependencyLoader] @@ -211,8 +219,8 @@ namespace osu.Game.Screens.Ranking topLayerBackground.FadeColour(expanded_top_layer_colour, RESIZE_DURATION, Easing.OutQuint); middleLayerBackground.FadeColour(expanded_middle_layer_colour, RESIZE_DURATION, Easing.OutQuint); - topLayerContentContainer.Add(topLayerContent = new ExpandedPanelTopContent(Score.User).With(d => d.Alpha = 0)); - middleLayerContentContainer.Add(middleLayerContent = new ExpandedPanelMiddleContent(Score, displayWithFlair).With(d => d.Alpha = 0)); + topLayerContentContainer.Add(topLayerContent = new ExpandedPanelTopContent(Score.User) { Alpha = 0 }); + middleLayerContentContainer.Add(middleLayerContent = new ExpandedPanelMiddleContent(Score, displayWithFlair) { Alpha = 0 }); // only the first expanded display should happen with flair. displayWithFlair = false; @@ -224,8 +232,13 @@ namespace osu.Game.Screens.Ranking topLayerBackground.FadeColour(contracted_top_layer_colour, RESIZE_DURATION, Easing.OutQuint); middleLayerBackground.FadeColour(contracted_middle_layer_colour, RESIZE_DURATION, Easing.OutQuint); - topLayerContentContainer.Add(topLayerContent = new ContractedPanelTopContent(Score).With(d => d.Alpha = 0)); - middleLayerContentContainer.Add(middleLayerContent = new ContractedPanelMiddleContent(Score).With(d => d.Alpha = 0)); + topLayerContentContainer.Add(topLayerContent = new ContractedPanelTopContent + { + ScorePosition = { BindTarget = ScorePosition }, + Alpha = 0 + }); + + middleLayerContentContainer.Add(middleLayerContent = new ContractedPanelMiddleContent(Score) { Alpha = 0 }); break; } From fd979a52fef9597b7e732bc8a1750723168fc520 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Mon, 13 Dec 2021 07:15:21 +0900 Subject: [PATCH 081/146] Increase score submission request timeout to 60s --- osu.Game/Online/Rooms/SubmitRoomScoreRequest.cs | 1 + osu.Game/Online/Solo/SubmitSoloScoreRequest.cs | 1 + 2 files changed, 2 insertions(+) diff --git a/osu.Game/Online/Rooms/SubmitRoomScoreRequest.cs b/osu.Game/Online/Rooms/SubmitRoomScoreRequest.cs index d5da6c401c..4ee4be6164 100644 --- a/osu.Game/Online/Rooms/SubmitRoomScoreRequest.cs +++ b/osu.Game/Online/Rooms/SubmitRoomScoreRequest.cs @@ -31,6 +31,7 @@ namespace osu.Game.Online.Rooms req.ContentType = "application/json"; req.Method = HttpMethod.Put; + req.Timeout = 60000; req.AddRaw(JsonConvert.SerializeObject(score, new JsonSerializerSettings { diff --git a/osu.Game/Online/Solo/SubmitSoloScoreRequest.cs b/osu.Game/Online/Solo/SubmitSoloScoreRequest.cs index 25c2e5a61f..763fcf3f20 100644 --- a/osu.Game/Online/Solo/SubmitSoloScoreRequest.cs +++ b/osu.Game/Online/Solo/SubmitSoloScoreRequest.cs @@ -31,6 +31,7 @@ namespace osu.Game.Online.Solo req.ContentType = "application/json"; req.Method = HttpMethod.Put; + req.Timeout = 60000; req.AddRaw(JsonConvert.SerializeObject(score, new JsonSerializerSettings { From d0fbbf110b74761e210937b94fd2f8bffd6bb5b9 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 13 Dec 2021 06:48:15 +0300 Subject: [PATCH 082/146] Expose `ScreenContainer` for access in `OsuGameDesktop` --- osu.Game/OsuGame.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 565ee7e71d..a35191613c 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -103,7 +103,7 @@ namespace osu.Game private Container topMostOverlayContent; - private ScalingContainer screenContainer; + protected ScalingContainer ScreenContainer { get; private set; } protected Container ScreenOffsetContainer { get; private set; } @@ -179,7 +179,7 @@ namespace osu.Game } private void updateBlockingOverlayFade() => - screenContainer.FadeColour(visibleBlockingOverlays.Any() ? OsuColour.Gray(0.5f) : Color4.White, 500, Easing.OutQuint); + ScreenContainer.FadeColour(visibleBlockingOverlays.Any() ? OsuColour.Gray(0.5f) : Color4.White, 500, Easing.OutQuint); public void AddBlockingOverlay(OverlayContainer overlay) { @@ -698,7 +698,7 @@ namespace osu.Game RelativeSizeAxes = Axes.Both, Children = new Drawable[] { - screenContainer = new ScalingContainer(ScalingMode.ExcludeOverlays) + ScreenContainer = new ScalingContainer(ScalingMode.ExcludeOverlays) { RelativeSizeAxes = Axes.Both, Anchor = Anchor.Centre, @@ -801,7 +801,7 @@ namespace osu.Game loadComponentSingleFile(userProfile = new UserProfileOverlay(), overlayContent.Add, true); loadComponentSingleFile(beatmapSetOverlay = new BeatmapSetOverlay(), overlayContent.Add, true); loadComponentSingleFile(wikiOverlay = new WikiOverlay(), overlayContent.Add, true); - loadComponentSingleFile(skinEditor = new SkinEditorOverlay(screenContainer), overlayContent.Add, true); + loadComponentSingleFile(skinEditor = new SkinEditorOverlay(ScreenContainer), overlayContent.Add, true); loadComponentSingleFile(new LoginOverlay { From d92f5039cd78edf204e9b40ede9f72869191019f Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 13 Dec 2021 06:48:40 +0300 Subject: [PATCH 083/146] Reorder version overlay to display behind game-wide overlays --- osu.Desktop/OsuGameDesktop.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Desktop/OsuGameDesktop.cs b/osu.Desktop/OsuGameDesktop.cs index 645ea66654..b234207848 100644 --- a/osu.Desktop/OsuGameDesktop.cs +++ b/osu.Desktop/OsuGameDesktop.cs @@ -70,7 +70,9 @@ namespace osu.Desktop if (!string.IsNullOrEmpty(stableInstallPath) && checkExists(stableInstallPath)) return stableInstallPath; } - catch { } + catch + { + } } stableInstallPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), @"osu!"); @@ -113,7 +115,7 @@ namespace osu.Desktop base.LoadComplete(); if (!noVersionOverlay) - LoadComponentAsync(versionManager = new VersionManager { Depth = int.MinValue }, Add); + LoadComponentAsync(versionManager = new VersionManager { Depth = int.MinValue }, ScreenContainer.Add); LoadComponentAsync(new DiscordRichPresence(), Add); From c097dc80488ff45357b33eb907c12bb8a34629db Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 13 Dec 2021 13:39:32 +0900 Subject: [PATCH 084/146] Add note about reasoning behind `MultiplayerClient.GetAPIBeatmap` call --- osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs index a877c6aca9..e1f7ea5e92 100644 --- a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs +++ b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs @@ -166,6 +166,8 @@ namespace osu.Game.Screens.OnlinePlay IBeatmapInfo foundBeatmap; if (multiplayerClient != null) + // This call can eventually go away (and use the else case below). + // Currently required only due to the method being overridden to provide special behaviour in tests. foundBeatmap = await multiplayerClient.GetAPIBeatmap(Item.BeatmapID).ConfigureAwait(false); else foundBeatmap = await beatmapLookupCache.GetBeatmapAsync(Item.BeatmapID).ConfigureAwait(false); From cac684c0448d69f5a3d427c7b0a375e5266dd93a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 13 Dec 2021 14:46:49 +0900 Subject: [PATCH 085/146] Improve appearance of player-wide background after failing with low background dim --- osu.Game/Screens/Play/FailAnimation.cs | 10 ++++++++++ osu.Game/Screens/Play/Player.cs | 2 ++ 2 files changed, 12 insertions(+) diff --git a/osu.Game/Screens/Play/FailAnimation.cs b/osu.Game/Screens/Play/FailAnimation.cs index 193e1e4129..aa777f18a8 100644 --- a/osu.Game/Screens/Play/FailAnimation.cs +++ b/osu.Game/Screens/Play/FailAnimation.cs @@ -6,6 +6,7 @@ using osu.Framework.Bindables; using osu.Game.Rulesets.UI; using System; using System.Collections.Generic; +using JetBrains.Annotations; using ManagedBass.Fx; using osu.Framework.Allocation; using osu.Framework.Audio.Sample; @@ -18,6 +19,7 @@ using osu.Framework.Utils; using osu.Game.Audio.Effects; using osu.Game.Beatmaps; using osu.Game.Configuration; +using osu.Game.Graphics; using osu.Game.Rulesets.Objects.Drawables; using osuTK; using osuTK.Graphics; @@ -58,6 +60,12 @@ namespace osu.Game.Screens.Play RelativeSizeAxes = Axes.Both, }; + /// + /// The player screen background, used to adjust appearance on failing. + /// + [CanBeNull] + public BackgroundScreen Background { private get; set; } + public FailAnimation(DrawableRuleset drawableRuleset) { this.drawableRuleset = drawableRuleset; @@ -136,6 +144,8 @@ namespace osu.Game.Screens.Play Content.ScaleTo(0.85f, duration, Easing.OutQuart); Content.RotateTo(1, duration, Easing.OutQuart); Content.FadeColour(Color4.Gray, duration); + + Background?.FadeColour(OsuColour.Gray(0.3f), 60); } public void RemoveFilters(bool resetTrackFrequency = true) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index e2896c174f..745e1f9e7c 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -921,6 +921,8 @@ namespace osu.Game.Screens.Play b.IsBreakTime.BindTo(breakTracker.IsBreakTime); b.StoryboardReplacesBackground.BindTo(storyboardReplacesBackground); + + failAnimationLayer.Background = b; }); HUDOverlay.IsBreakTime.BindTo(breakTracker.IsBreakTime); From 4cac87e93323a46752fb2353ed41488ba0f332b7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 13 Dec 2021 15:26:32 +0900 Subject: [PATCH 086/146] Add test coverage showing that `KeyBindingStore` won't remove excess key bindings --- .../Database/TestRealmKeyBindingStore.cs | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/osu.Game.Tests/Database/TestRealmKeyBindingStore.cs b/osu.Game.Tests/Database/TestRealmKeyBindingStore.cs index 860828ae81..f05d9ab3dc 100644 --- a/osu.Game.Tests/Database/TestRealmKeyBindingStore.cs +++ b/osu.Game.Tests/Database/TestRealmKeyBindingStore.cs @@ -52,6 +52,45 @@ namespace osu.Game.Tests.Database Assert.That(queryCount(GlobalAction.Select), Is.EqualTo(2)); } + [Test] + public void TestDefaultsPopulationRemovesExcess() + { + Assert.That(queryCount(), Is.EqualTo(0)); + + KeyBindingContainer testContainer = new TestKeyBindingContainer(); + + // Add some excess bindings for an action which only supports 1. + using (var realm = realmContextFactory.CreateContext()) + using (var transaction = realm.BeginWrite()) + { + realm.Add(new RealmKeyBinding + { + Action = GlobalAction.Back, + KeyCombination = new KeyCombination(InputKey.A) + }); + + realm.Add(new RealmKeyBinding + { + Action = GlobalAction.Back, + KeyCombination = new KeyCombination(InputKey.S) + }); + + realm.Add(new RealmKeyBinding + { + Action = GlobalAction.Back, + KeyCombination = new KeyCombination(InputKey.D) + }); + + transaction.Commit(); + } + + Assert.That(queryCount(GlobalAction.Back), Is.EqualTo(3)); + + keyBindingStore.Register(testContainer, Enumerable.Empty()); + + Assert.That(queryCount(GlobalAction.Back), Is.EqualTo(1)); + } + private int queryCount(GlobalAction? match = null) { using (var realm = realmContextFactory.CreateContext()) From 7318ff3f98cee2e689d1f20ea736a8ec6b4fdd3d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 13 Dec 2021 15:26:49 +0900 Subject: [PATCH 087/146] Refactor `KeyBindingStore` to clean up any excess bindings for individual actions While this isn't strictly required (outside of rulesets, potentially), I think it's best that we keep these counts in a sane state. Right now, it will remove any excess. Arguably, in the case an entry is found with too many, we should wipe all entries and re-populate the defaults. Interested in opinions on that before merging. See https://github.com/ppy/osu/discussions/15925 for an example where wiping may be the more appropriate behaviour. --- osu.Game/Input/RealmKeyBindingStore.cs | 39 ++++++++++++++++++-------- 1 file changed, 28 insertions(+), 11 deletions(-) diff --git a/osu.Game/Input/RealmKeyBindingStore.cs b/osu.Game/Input/RealmKeyBindingStore.cs index 3bdb0a180d..cb51797685 100644 --- a/osu.Game/Input/RealmKeyBindingStore.cs +++ b/osu.Game/Input/RealmKeyBindingStore.cs @@ -81,20 +81,37 @@ namespace osu.Game.Input // compare counts in database vs defaults for each action type. foreach (var defaultsForAction in defaults.GroupBy(k => k.Action)) { - // avoid performing redundant queries when the database is empty and needs to be re-filled. - int existingCount = existingBindings.Count(k => k.RulesetName == rulesetName && k.Variant == variant && k.ActionInt == (int)defaultsForAction.Key); + IEnumerable existing = existingBindings.Where(k => + k.RulesetName == rulesetName + && k.Variant == variant + && k.ActionInt == (int)defaultsForAction.Key); - if (defaultsForAction.Count() <= existingCount) - continue; + int defaultsCount = defaultsForAction.Count(); + int existingCount = existing.Count(); - // insert any defaults which are missing. - realm.Add(defaultsForAction.Skip(existingCount).Select(k => new RealmKeyBinding + if (defaultsCount > existingCount) { - KeyCombinationString = k.KeyCombination.ToString(), - ActionInt = (int)k.Action, - RulesetName = rulesetName, - Variant = variant - })); + // insert any defaults which are missing. + realm.Add(defaultsForAction.Skip(existingCount).Select(k => new RealmKeyBinding + { + KeyCombinationString = k.KeyCombination.ToString(), + ActionInt = (int)k.Action, + RulesetName = rulesetName, + Variant = variant + })); + } + else if (defaultsCount < existingCount) + { + // generally this shouldn't happen, but if the user has more key bindings for an action than we expect, + // remove the last entries until the count matches for sanity. + foreach (var k in existing.TakeLast(existingCount - defaultsCount).ToArray()) + { + realm.Remove(k); + + // Remove from the local flattened/cached list so future lookups don't query now deleted rows. + existingBindings.Remove(k); + } + } } } From 70045494babd97eec564f624e3a5a38a1019ebdc Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Mon, 13 Dec 2021 16:09:53 +0900 Subject: [PATCH 088/146] Re-simplify code by removing BeatmapManager event instead --- .../Screens/OnlinePlay/Match/RoomSubScreen.cs | 36 ++++--------------- .../Multiplayer/MultiplayerMatchSubScreen.cs | 24 ++----------- 2 files changed, 9 insertions(+), 51 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs index 6b395058fa..a560d85b7d 100644 --- a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs @@ -15,7 +15,6 @@ using osu.Framework.Graphics.Shapes; using osu.Framework.Screens; using osu.Game.Audio; using osu.Game.Beatmaps; -using osu.Game.Online; using osu.Game.Online.Rooms; using osu.Game.Overlays; using osu.Game.Overlays.Mods; @@ -68,7 +67,7 @@ namespace osu.Game.Screens.OnlinePlay.Match protected OnlinePlayScreen ParentScreen { get; private set; } [Cached] - private OnlinePlayBeatmapAvailabilityTracker beatmapAvailabilityTracker { get; set; } + private readonly OnlinePlayBeatmapAvailabilityTracker beatmapAvailabilityTracker = new OnlinePlayBeatmapAvailabilityTracker(); protected IBindable BeatmapAvailability => beatmapAvailabilityTracker.Availability; @@ -91,11 +90,6 @@ namespace osu.Game.Screens.OnlinePlay.Match Padding = new MarginPadding { Top = Header.HEIGHT }; - beatmapAvailabilityTracker = new OnlinePlayBeatmapAvailabilityTracker - { - SelectedItem = { BindTarget = SelectedItem } - }; - RoomId.BindTo(room.RoomID); } @@ -248,10 +242,10 @@ namespace osu.Game.Screens.OnlinePlay.Match }, true); SelectedItem.BindValueChanged(_ => Scheduler.AddOnce(selectedItemChanged)); - - beatmapManager.ItemUpdated += beatmapUpdated; - UserMods.BindValueChanged(_ => Scheduler.AddOnce(UpdateMods)); + + beatmapAvailabilityTracker.SelectedItem.BindTo(SelectedItem); + beatmapAvailabilityTracker.Availability.BindValueChanged(_ => updateWorkingBeatmap()); } protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) @@ -303,7 +297,7 @@ namespace osu.Game.Screens.OnlinePlay.Match public override void OnResuming(IScreen last) { base.OnResuming(last); - UpdateWorkingBeatmap(); + updateWorkingBeatmap(); beginHandlingTrack(); Scheduler.AddOnce(UpdateMods); } @@ -346,7 +340,7 @@ namespace osu.Game.Screens.OnlinePlay.Match private void selectedItemChanged() { - UpdateWorkingBeatmap(); + updateWorkingBeatmap(); var selected = SelectedItem.Value; @@ -375,16 +369,8 @@ namespace osu.Game.Screens.OnlinePlay.Match } } - private void beatmapUpdated(BeatmapSetInfo set) => Schedule(UpdateWorkingBeatmap); - - protected virtual void UpdateWorkingBeatmap() + private void updateWorkingBeatmap() { - if (BeatmapAvailability.Value.State != DownloadState.LocallyAvailable) - { - Beatmap.Value = beatmapManager.GetWorkingBeatmap(null); - return; - } - var beatmap = SelectedItem.Value?.Beatmap.Value; // Retrieve the corresponding local beatmap, since we can't directly use the playlist's beatmap info @@ -450,14 +436,6 @@ namespace osu.Game.Screens.OnlinePlay.Match /// The room to change the settings of. protected abstract RoomSettingsOverlay CreateRoomSettingsOverlay(Room room); - protected override void Dispose(bool isDisposing) - { - base.Dispose(isDisposing); - - if (beatmapManager != null) - beatmapManager.ItemUpdated -= beatmapUpdated; - } - public class UserModSelectButton : PurpleTriangleButton { } diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs index 4fa21cb847..6895608c8e 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs @@ -15,7 +15,6 @@ using osu.Framework.Screens; using osu.Framework.Threading; using osu.Game.Beatmaps; using osu.Game.Configuration; -using osu.Game.Extensions; using osu.Game.Online; using osu.Game.Online.Multiplayer; using osu.Game.Online.Rooms; @@ -327,27 +326,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer if (client.LocalUser?.State == MultiplayerUserState.Ready) client.ChangeState(MultiplayerUserState.Idle); } - - UpdateWorkingBeatmap(); - } - - protected override void UpdateWorkingBeatmap() - { - var lastBeatmap = Beatmap.Value; - - base.UpdateWorkingBeatmap(); - - // Nothing to do if the beatmap hasn't changed. - if (Beatmap.Value.BeatmapInfo.MatchesOnlineID(lastBeatmap.BeatmapInfo)) - return; - - // The selected item is nulled during the beatmap query. During this, the working beatmap will be the dummy beatmap. - // We don't want to enter spectate mode with the dummy beatmap. - if (!Beatmap.Value.BeatmapInfo.MatchesOnlineID(SelectedItem.Value?.Beatmap.Value)) - return; - - if (client.LocalUser?.State == MultiplayerUserState.Spectating - && (client.Room?.State == MultiplayerRoomState.WaitingForLoad || client.Room?.State == MultiplayerRoomState.Playing)) + else if (client.LocalUser?.State == MultiplayerUserState.Spectating + && (client.Room?.State == MultiplayerRoomState.WaitingForLoad || client.Room?.State == MultiplayerRoomState.Playing)) { onLoadRequested(); } From b0d14526eac084234a7b4c2e1d30f63a36262f35 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 13 Dec 2021 16:34:48 +0900 Subject: [PATCH 089/146] Move test `ScoreInfo` creation to `TestResources` The main goal here is to remove the inheritance, since realm doesn't like that. Unfortunate that we can't use object initialisers in a few of these places, but no real way around that. --- .../Formats/LegacyScoreDecoderTest.cs | 2 +- osu.Game.Tests/Resources/TestResources.cs | 67 +++++++++++++++++++ .../TestScenePlaylistsResultsScreen.cs | 13 +++- .../TestSceneContractedPanelMiddleContent.cs | 5 +- .../TestSceneExpandedPanelMiddleContent.cs | 28 +++----- .../TestSceneExpandedPanelTopContent.cs | 4 +- .../Visual/Ranking/TestSceneResultsScreen.cs | 20 +++--- .../Visual/Ranking/TestSceneScorePanel.cs | 42 +++++++++--- .../Visual/Ranking/TestSceneScorePanelList.cs | 47 ++++++++----- .../Ranking/TestSceneStatisticsPanel.cs | 15 ++--- osu.Game/Tests/TestScoreInfo.cs | 66 ------------------ 11 files changed, 170 insertions(+), 139 deletions(-) delete mode 100644 osu.Game/Tests/TestScoreInfo.cs diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyScoreDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyScoreDecoderTest.cs index a73ae9dcdb..81d89359e0 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyScoreDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyScoreDecoderTest.cs @@ -62,7 +62,7 @@ namespace osu.Game.Tests.Beatmaps.Formats public void TestCultureInvariance() { var ruleset = new OsuRuleset().RulesetInfo; - var scoreInfo = new TestScoreInfo(ruleset); + var scoreInfo = TestResources.CreateTestScoreInfo(ruleset); var beatmap = new TestBeatmap(ruleset); var score = new Score { diff --git a/osu.Game.Tests/Resources/TestResources.cs b/osu.Game.Tests/Resources/TestResources.cs index 440d5e701f..7535f6769f 100644 --- a/osu.Game.Tests/Resources/TestResources.cs +++ b/osu.Game.Tests/Resources/TestResources.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.IO; +using System.Linq; using System.Text; using System.Threading; using NUnit.Framework; @@ -12,8 +13,12 @@ using osu.Framework.IO.Stores; using osu.Framework.Testing; using osu.Framework.Utils; using osu.Game.Beatmaps; +using osu.Game.Online.API.Requests.Responses; using osu.Game.Rulesets; +using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu; +using osu.Game.Rulesets.Scoring; +using osu.Game.Scoring; namespace osu.Game.Tests.Resources { @@ -137,5 +142,67 @@ namespace osu.Game.Tests.Resources } } } + + /// + /// Create a test score model. + /// + /// The ruleset for which the score was set against. + /// Whether to include an excessive number of mods. If false, only two will be added. + /// + public static ScoreInfo CreateTestScoreInfo(RulesetInfo ruleset = null, bool excessMods = false) => + CreateTestScoreInfo(CreateTestBeatmapSetInfo(1, new[] { ruleset ?? new OsuRuleset().RulesetInfo }).Beatmaps.First(), excessMods); + + /// + /// Create a test score model. + /// + /// The beatmap for which the score was set against. + /// Whether to include an excessive number of mods. If false, only two will be added. + /// + public static ScoreInfo CreateTestScoreInfo(BeatmapInfo beatmap, bool excessMods = false) => new ScoreInfo + { + User = new APIUser + { + Id = 2, + Username = "peppy", + CoverUrl = "https://osu.ppy.sh/images/headers/profile-covers/c3.jpg", + }, + BeatmapInfo = beatmap, + Ruleset = beatmap.Ruleset, + RulesetID = beatmap.Ruleset.ID ?? 0, + Mods = excessMods + ? beatmap.Ruleset.CreateInstance().CreateAllMods().ToArray() + : new Mod[] { new TestModHardRock(), new TestModDoubleTime() }, + TotalScore = 2845370, + Accuracy = 0.95, + MaxCombo = 999, + Position = 1, + Rank = ScoreRank.S, + Date = DateTimeOffset.Now, + Statistics = new Dictionary + { + [HitResult.Miss] = 1, + [HitResult.Meh] = 50, + [HitResult.Ok] = 100, + [HitResult.Good] = 200, + [HitResult.Great] = 300, + [HitResult.Perfect] = 320, + [HitResult.SmallTickHit] = 50, + [HitResult.SmallTickMiss] = 25, + [HitResult.LargeTickHit] = 100, + [HitResult.LargeTickMiss] = 50, + [HitResult.SmallBonus] = 10, + [HitResult.SmallBonus] = 50 + }, + }; + + private class TestModHardRock : ModHardRock + { + public override double ScoreMultiplier => 1; + } + + private class TestModDoubleTime : ModDoubleTime + { + public override double ScoreMultiplier => 1; + } } } diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs index 64fc4797a0..faa0ce2bdc 100644 --- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs +++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs @@ -22,6 +22,7 @@ using osu.Game.Scoring; using osu.Game.Screens.OnlinePlay.Playlists; using osu.Game.Screens.Ranking; using osu.Game.Tests.Beatmaps; +using osu.Game.Tests.Resources; namespace osu.Game.Tests.Visual.Playlists { @@ -52,7 +53,9 @@ namespace osu.Game.Tests.Visual.Playlists AddStep("bind user score info handler", () => { - userScore = new TestScoreInfo(new OsuRuleset().RulesetInfo) { OnlineID = currentScoreId++ }; + userScore = TestResources.CreateTestScoreInfo(new OsuRuleset().RulesetInfo); + userScore.OnlineID = currentScoreId++; + bindHandler(userScore: userScore); }); @@ -78,7 +81,9 @@ namespace osu.Game.Tests.Visual.Playlists AddStep("bind user score info handler", () => { - userScore = new TestScoreInfo(new OsuRuleset().RulesetInfo) { OnlineID = currentScoreId++ }; + userScore = TestResources.CreateTestScoreInfo(new OsuRuleset().RulesetInfo); + userScore.OnlineID = currentScoreId++; + bindHandler(true, userScore); }); @@ -127,7 +132,9 @@ namespace osu.Game.Tests.Visual.Playlists AddStep("bind user score info handler", () => { - userScore = new TestScoreInfo(new OsuRuleset().RulesetInfo) { OnlineID = currentScoreId++ }; + userScore = TestResources.CreateTestScoreInfo(new OsuRuleset().RulesetInfo); + userScore.OnlineID = currentScoreId++; + bindHandler(userScore: userScore); }); diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneContractedPanelMiddleContent.cs b/osu.Game.Tests/Visual/Ranking/TestSceneContractedPanelMiddleContent.cs index f246560c82..04ddc5d7f0 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneContractedPanelMiddleContent.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneContractedPanelMiddleContent.cs @@ -13,6 +13,7 @@ using osu.Game.Rulesets.Osu; using osu.Game.Scoring; using osu.Game.Screens.Ranking; using osu.Game.Screens.Ranking.Contracted; +using osu.Game.Tests.Resources; using osuTK; namespace osu.Game.Tests.Visual.Ranking @@ -22,13 +23,13 @@ namespace osu.Game.Tests.Visual.Ranking [Test] public void TestShowPanel() { - AddStep("show example score", () => showPanel(CreateWorkingBeatmap(CreateBeatmap(new OsuRuleset().RulesetInfo)), new TestScoreInfo(new OsuRuleset().RulesetInfo))); + AddStep("show example score", () => showPanel(CreateWorkingBeatmap(CreateBeatmap(new OsuRuleset().RulesetInfo)), TestResources.CreateTestScoreInfo(new OsuRuleset().RulesetInfo))); } [Test] public void TestExcessMods() { - AddStep("show excess mods score", () => showPanel(CreateWorkingBeatmap(CreateBeatmap(new OsuRuleset().RulesetInfo)), new TestScoreInfo(new OsuRuleset().RulesetInfo, true))); + AddStep("show excess mods score", () => showPanel(CreateWorkingBeatmap(CreateBeatmap(new OsuRuleset().RulesetInfo)), TestResources.CreateTestScoreInfo(new OsuRuleset().RulesetInfo, true))); } private void showPanel(WorkingBeatmap workingBeatmap, ScoreInfo score) diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneExpandedPanelMiddleContent.cs b/osu.Game.Tests/Visual/Ranking/TestSceneExpandedPanelMiddleContent.cs index 9983993d9c..1f34606fcb 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneExpandedPanelMiddleContent.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneExpandedPanelMiddleContent.cs @@ -20,6 +20,7 @@ using osu.Game.Scoring; using osu.Game.Screens.Ranking; using osu.Game.Screens.Ranking.Expanded; using osu.Game.Tests.Beatmaps; +using osu.Game.Tests.Resources; using osuTK; namespace osu.Game.Tests.Visual.Ranking @@ -34,10 +35,7 @@ namespace osu.Game.Tests.Visual.Ranking { var author = new APIUser { Username = "mapper_name" }; - AddStep("show example score", () => showPanel(new TestScoreInfo(new OsuRuleset().RulesetInfo) - { - BeatmapInfo = createTestBeatmap(author) - })); + AddStep("show example score", () => showPanel(TestResources.CreateTestScoreInfo(createTestBeatmap(author)))); } [Test] @@ -45,10 +43,7 @@ namespace osu.Game.Tests.Visual.Ranking { var author = new APIUser { Username = "mapper_name" }; - AddStep("show excess mods score", () => showPanel(new TestScoreInfo(new OsuRuleset().RulesetInfo, true) - { - BeatmapInfo = createTestBeatmap(author) - })); + AddStep("show excess mods score", () => showPanel(TestResources.CreateTestScoreInfo(createTestBeatmap(author), true))); AddAssert("mapper name present", () => this.ChildrenOfType().Any(spriteText => spriteText.Current.Value == "mapper_name")); } @@ -56,10 +51,7 @@ namespace osu.Game.Tests.Visual.Ranking [Test] public void TestMapWithUnknownMapper() { - AddStep("show example score", () => showPanel(new TestScoreInfo(new OsuRuleset().RulesetInfo) - { - BeatmapInfo = createTestBeatmap(new APIUser()) - })); + AddStep("show example score", () => showPanel(TestResources.CreateTestScoreInfo(createTestBeatmap(new APIUser())))); AddAssert("mapped by text not present", () => this.ChildrenOfType().All(spriteText => !containsAny(spriteText.Text.ToString(), "mapped", "by"))); @@ -77,12 +69,12 @@ namespace osu.Game.Tests.Visual.Ranking var mods = new Mod[] { ruleset.GetAutoplayMod() }; var beatmap = createTestBeatmap(new APIUser()); - showPanel(new TestScoreInfo(ruleset.RulesetInfo) - { - Mods = mods, - BeatmapInfo = beatmap, - Date = default, - }); + var score = TestResources.CreateTestScoreInfo(beatmap); + + score.Mods = mods; + score.Date = default; + + showPanel(score); }); AddAssert("play time not displayed", () => !this.ChildrenOfType().Any()); diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneExpandedPanelTopContent.cs b/osu.Game.Tests/Visual/Ranking/TestSceneExpandedPanelTopContent.cs index a32bcbe7f0..a2fa142896 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneExpandedPanelTopContent.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneExpandedPanelTopContent.cs @@ -5,8 +5,8 @@ using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; -using osu.Game.Rulesets.Osu; using osu.Game.Screens.Ranking.Expanded; +using osu.Game.Tests.Resources; using osuTK; namespace osu.Game.Tests.Visual.Ranking @@ -27,7 +27,7 @@ namespace osu.Game.Tests.Visual.Ranking RelativeSizeAxes = Axes.Both, Colour = Color4Extensions.FromHex("#444"), }, - new ExpandedPanelTopContent(new TestScoreInfo(new OsuRuleset().RulesetInfo).User), + new ExpandedPanelTopContent(TestResources.CreateTestScoreInfo().User), } }; } diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs b/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs index 94700bac6a..809f513a83 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs @@ -21,6 +21,7 @@ using osu.Game.Screens; using osu.Game.Screens.Play; using osu.Game.Screens.Ranking; using osu.Game.Screens.Ranking.Statistics; +using osu.Game.Tests.Resources; using osuTK; using osuTK.Input; @@ -72,11 +73,10 @@ namespace osu.Game.Tests.Visual.Ranking { TestResultsScreen screen = null; - var score = new TestScoreInfo(new OsuRuleset().RulesetInfo) - { - Accuracy = accuracy, - Rank = rank - }; + var score = TestResources.CreateTestScoreInfo(); + + score.Accuracy = accuracy; + score.Rank = rank; AddStep("load results", () => Child = new TestResultsContainer(screen = createResultsScreen(score))); AddUntilStep("wait for loaded", () => screen.IsLoaded); @@ -204,7 +204,7 @@ namespace osu.Game.Tests.Visual.Ranking { DelayedFetchResultsScreen screen = null; - AddStep("load results", () => Child = new TestResultsContainer(screen = new DelayedFetchResultsScreen(new TestScoreInfo(new OsuRuleset().RulesetInfo), 3000))); + AddStep("load results", () => Child = new TestResultsContainer(screen = new DelayedFetchResultsScreen(TestResources.CreateTestScoreInfo(new OsuRuleset().RulesetInfo), 3000))); AddUntilStep("wait for loaded", () => screen.IsLoaded); AddStep("click expanded panel", () => { @@ -237,9 +237,9 @@ namespace osu.Game.Tests.Visual.Ranking AddAssert("download button is enabled", () => screen.ChildrenOfType().Last().Enabled.Value); } - private TestResultsScreen createResultsScreen(ScoreInfo score = null) => new TestResultsScreen(score ?? new TestScoreInfo(new OsuRuleset().RulesetInfo)); + private TestResultsScreen createResultsScreen(ScoreInfo score = null) => new TestResultsScreen(score ?? TestResources.CreateTestScoreInfo(new OsuRuleset().RulesetInfo)); - private UnrankedSoloResultsScreen createUnrankedSoloResultsScreen() => new UnrankedSoloResultsScreen(new TestScoreInfo(new OsuRuleset().RulesetInfo)); + private UnrankedSoloResultsScreen createUnrankedSoloResultsScreen() => new UnrankedSoloResultsScreen(TestResources.CreateTestScoreInfo(new OsuRuleset().RulesetInfo)); private class TestResultsContainer : Container { @@ -282,7 +282,7 @@ namespace osu.Game.Tests.Visual.Ranking for (int i = 0; i < 20; i++) { - var score = new TestScoreInfo(new OsuRuleset().RulesetInfo); + var score = TestResources.CreateTestScoreInfo(new OsuRuleset().RulesetInfo); score.TotalScore += 10 - i; score.Hash = $"test{i}"; scores.Add(score); @@ -316,7 +316,7 @@ namespace osu.Game.Tests.Visual.Ranking for (int i = 0; i < 20; i++) { - var score = new TestScoreInfo(new OsuRuleset().RulesetInfo); + var score = TestResources.CreateTestScoreInfo(); score.TotalScore += 10 - i; scores.Add(score); } diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneScorePanel.cs b/osu.Game.Tests/Visual/Ranking/TestSceneScorePanel.cs index 5af55e99f8..5dbeefd390 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneScorePanel.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneScorePanel.cs @@ -3,10 +3,10 @@ using NUnit.Framework; using osu.Framework.Graphics; -using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; using osu.Game.Screens.Ranking; +using osu.Game.Tests.Resources; namespace osu.Game.Tests.Visual.Ranking { @@ -17,7 +17,9 @@ namespace osu.Game.Tests.Visual.Ranking [Test] public void TestDRank() { - var score = new TestScoreInfo(new OsuRuleset().RulesetInfo) { Accuracy = 0.5, Rank = ScoreRank.D }; + var score = TestResources.CreateTestScoreInfo(); + score.Accuracy = 0.5; + score.Rank = ScoreRank.D; addPanelStep(score); } @@ -25,7 +27,9 @@ namespace osu.Game.Tests.Visual.Ranking [Test] public void TestCRank() { - var score = new TestScoreInfo(new OsuRuleset().RulesetInfo) { Accuracy = 0.75, Rank = ScoreRank.C }; + var score = TestResources.CreateTestScoreInfo(); + score.Accuracy = 0.75; + score.Rank = ScoreRank.C; addPanelStep(score); } @@ -33,7 +37,9 @@ namespace osu.Game.Tests.Visual.Ranking [Test] public void TestBRank() { - var score = new TestScoreInfo(new OsuRuleset().RulesetInfo) { Accuracy = 0.85, Rank = ScoreRank.B }; + var score = TestResources.CreateTestScoreInfo(); + score.Accuracy = 0.85; + score.Rank = ScoreRank.B; addPanelStep(score); } @@ -41,7 +47,9 @@ namespace osu.Game.Tests.Visual.Ranking [Test] public void TestARank() { - var score = new TestScoreInfo(new OsuRuleset().RulesetInfo) { Accuracy = 0.925, Rank = ScoreRank.A }; + var score = TestResources.CreateTestScoreInfo(); + score.Accuracy = 0.925; + score.Rank = ScoreRank.A; addPanelStep(score); } @@ -49,7 +57,9 @@ namespace osu.Game.Tests.Visual.Ranking [Test] public void TestSRank() { - var score = new TestScoreInfo(new OsuRuleset().RulesetInfo) { Accuracy = 0.975, Rank = ScoreRank.S }; + var score = TestResources.CreateTestScoreInfo(); + score.Accuracy = 0.975; + score.Rank = ScoreRank.S; addPanelStep(score); } @@ -57,7 +67,9 @@ namespace osu.Game.Tests.Visual.Ranking [Test] public void TestAlmostSSRank() { - var score = new TestScoreInfo(new OsuRuleset().RulesetInfo) { Accuracy = 0.9999, Rank = ScoreRank.S }; + var score = TestResources.CreateTestScoreInfo(); + score.Accuracy = 0.9999; + score.Rank = ScoreRank.S; addPanelStep(score); } @@ -65,7 +77,9 @@ namespace osu.Game.Tests.Visual.Ranking [Test] public void TestSSRank() { - var score = new TestScoreInfo(new OsuRuleset().RulesetInfo) { Accuracy = 1, Rank = ScoreRank.X }; + var score = TestResources.CreateTestScoreInfo(); + score.Accuracy = 1; + score.Rank = ScoreRank.X; addPanelStep(score); } @@ -73,7 +87,9 @@ namespace osu.Game.Tests.Visual.Ranking [Test] public void TestAllHitResults() { - var score = new TestScoreInfo(new OsuRuleset().RulesetInfo) { Statistics = { [HitResult.Perfect] = 350, [HitResult.Ok] = 200 } }; + var score = TestResources.CreateTestScoreInfo(); + score.Statistics[HitResult.Perfect] = 350; + score.Statistics[HitResult.Ok] = 200; addPanelStep(score); } @@ -81,7 +97,9 @@ namespace osu.Game.Tests.Visual.Ranking [Test] public void TestContractedPanel() { - var score = new TestScoreInfo(new OsuRuleset().RulesetInfo) { Accuracy = 0.925, Rank = ScoreRank.A }; + var score = TestResources.CreateTestScoreInfo(); + score.Accuracy = 0.925; + score.Rank = ScoreRank.A; addPanelStep(score, PanelState.Contracted); } @@ -89,7 +107,9 @@ namespace osu.Game.Tests.Visual.Ranking [Test] public void TestExpandAndContract() { - var score = new TestScoreInfo(new OsuRuleset().RulesetInfo) { Accuracy = 0.925, Rank = ScoreRank.A }; + var score = TestResources.CreateTestScoreInfo(); + score.Accuracy = 0.925; + score.Rank = ScoreRank.A; addPanelStep(score, PanelState.Contracted); AddWaitStep("wait for transition", 10); diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneScorePanelList.cs b/osu.Game.Tests/Visual/Ranking/TestSceneScorePanelList.cs index 2f9652d354..f963be5d81 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneScorePanelList.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneScorePanelList.cs @@ -10,6 +10,7 @@ using osu.Framework.Utils; using osu.Game.Rulesets.Osu; using osu.Game.Scoring; using osu.Game.Screens.Ranking; +using osu.Game.Tests.Resources; using osuTK.Input; namespace osu.Game.Tests.Visual.Ranking @@ -29,14 +30,14 @@ namespace osu.Game.Tests.Visual.Ranking { createListStep(() => new ScorePanelList { - SelectedScore = { Value = new TestScoreInfo(new OsuRuleset().RulesetInfo) } + SelectedScore = { Value = TestResources.CreateTestScoreInfo(new OsuRuleset().RulesetInfo) } }); } [Test] public void TestAddPanelAfterSelectingScore() { - var score = new TestScoreInfo(new OsuRuleset().RulesetInfo); + var score = TestResources.CreateTestScoreInfo(new OsuRuleset().RulesetInfo); createListStep(() => new ScorePanelList { @@ -52,7 +53,7 @@ namespace osu.Game.Tests.Visual.Ranking [Test] public void TestAddPanelBeforeSelectingScore() { - var score = new TestScoreInfo(new OsuRuleset().RulesetInfo); + var score = TestResources.CreateTestScoreInfo(new OsuRuleset().RulesetInfo); createListStep(() => new ScorePanelList()); @@ -75,7 +76,7 @@ namespace osu.Game.Tests.Visual.Ranking AddStep("add many scores", () => { for (int i = 0; i < 20; i++) - list.AddScore(new TestScoreInfo(new OsuRuleset().RulesetInfo)); + list.AddScore(TestResources.CreateTestScoreInfo(new OsuRuleset().RulesetInfo)); }); assertFirstPanelCentred(); @@ -84,7 +85,7 @@ namespace osu.Game.Tests.Visual.Ranking [Test] public void TestAddManyScoresAfterExpandedPanel() { - var initialScore = new TestScoreInfo(new OsuRuleset().RulesetInfo); + var initialScore = TestResources.CreateTestScoreInfo(new OsuRuleset().RulesetInfo); createListStep(() => new ScorePanelList()); @@ -97,7 +98,7 @@ namespace osu.Game.Tests.Visual.Ranking AddStep("add many scores", () => { for (int i = 0; i < 20; i++) - list.AddScore(new TestScoreInfo(new OsuRuleset().RulesetInfo) { TotalScore = initialScore.TotalScore - i - 1 }); + list.AddScore(createScoreForTotalScore(initialScore.TotalScore - i - 1)); }); assertScoreState(initialScore, true); @@ -107,7 +108,7 @@ namespace osu.Game.Tests.Visual.Ranking [Test] public void TestAddManyScoresBeforeExpandedPanel() { - var initialScore = new TestScoreInfo(new OsuRuleset().RulesetInfo); + var initialScore = TestResources.CreateTestScoreInfo(new OsuRuleset().RulesetInfo); createListStep(() => new ScorePanelList()); @@ -120,7 +121,7 @@ namespace osu.Game.Tests.Visual.Ranking AddStep("add scores", () => { for (int i = 0; i < 20; i++) - list.AddScore(new TestScoreInfo(new OsuRuleset().RulesetInfo) { TotalScore = initialScore.TotalScore + i + 1 }); + list.AddScore(createScoreForTotalScore(initialScore.TotalScore + i + 1)); }); assertScoreState(initialScore, true); @@ -130,7 +131,7 @@ namespace osu.Game.Tests.Visual.Ranking [Test] public void TestAddManyPanelsOnBothSidesOfExpandedPanel() { - var initialScore = new TestScoreInfo(new OsuRuleset().RulesetInfo); + var initialScore = TestResources.CreateTestScoreInfo(new OsuRuleset().RulesetInfo); createListStep(() => new ScorePanelList()); @@ -143,10 +144,10 @@ namespace osu.Game.Tests.Visual.Ranking AddStep("add scores after", () => { for (int i = 0; i < 20; i++) - list.AddScore(new TestScoreInfo(new OsuRuleset().RulesetInfo) { TotalScore = initialScore.TotalScore - i - 1 }); + list.AddScore(createScoreForTotalScore(initialScore.TotalScore - i - 1)); for (int i = 0; i < 20; i++) - list.AddScore(new TestScoreInfo(new OsuRuleset().RulesetInfo) { TotalScore = initialScore.TotalScore + i + 1 }); + list.AddScore(createScoreForTotalScore(initialScore.TotalScore + i + 1)); }); assertScoreState(initialScore, true); @@ -156,8 +157,8 @@ namespace osu.Game.Tests.Visual.Ranking [Test] public void TestSelectMultipleScores() { - var firstScore = new TestScoreInfo(new OsuRuleset().RulesetInfo); - var secondScore = new TestScoreInfo(new OsuRuleset().RulesetInfo); + var firstScore = TestResources.CreateTestScoreInfo(new OsuRuleset().RulesetInfo); + var secondScore = TestResources.CreateTestScoreInfo(new OsuRuleset().RulesetInfo); firstScore.UserString = "A"; secondScore.UserString = "B"; @@ -190,7 +191,7 @@ namespace osu.Game.Tests.Visual.Ranking [Test] public void TestAddScoreImmediately() { - var score = new TestScoreInfo(new OsuRuleset().RulesetInfo); + var score = TestResources.CreateTestScoreInfo(new OsuRuleset().RulesetInfo); createListStep(() => { @@ -206,9 +207,14 @@ namespace osu.Game.Tests.Visual.Ranking [Test] public void TestKeyboardNavigation() { - var lowestScore = new TestScoreInfo(new OsuRuleset().RulesetInfo) { MaxCombo = 100 }; - var middleScore = new TestScoreInfo(new OsuRuleset().RulesetInfo) { MaxCombo = 200 }; - var highestScore = new TestScoreInfo(new OsuRuleset().RulesetInfo) { MaxCombo = 300 }; + var lowestScore = TestResources.CreateTestScoreInfo(); + lowestScore.MaxCombo = 100; + + var middleScore = TestResources.CreateTestScoreInfo(); + middleScore.MaxCombo = 200; + + var highestScore = TestResources.CreateTestScoreInfo(); + highestScore.MaxCombo = 300; createListStep(() => new ScorePanelList()); @@ -270,6 +276,13 @@ namespace osu.Game.Tests.Visual.Ranking assertExpandedPanelCentred(); } + private ScoreInfo createScoreForTotalScore(long totalScore) + { + var score = TestResources.CreateTestScoreInfo(); + score.TotalScore = totalScore; + return score; + } + private void createListStep(Func creationFunc) { AddStep("create list", () => Child = list = creationFunc().With(d => diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneStatisticsPanel.cs b/osu.Game.Tests/Visual/Ranking/TestSceneStatisticsPanel.cs index d91aec753c..ebea523b9e 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneStatisticsPanel.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneStatisticsPanel.cs @@ -11,6 +11,7 @@ using osu.Game.Scoring; using osu.Game.Screens.Ranking.Statistics; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Scoring; +using osu.Game.Tests.Resources; using osuTK; namespace osu.Game.Tests.Visual.Ranking @@ -20,10 +21,8 @@ namespace osu.Game.Tests.Visual.Ranking [Test] public void TestScoreWithTimeStatistics() { - var score = new TestScoreInfo(new OsuRuleset().RulesetInfo) - { - HitEvents = TestSceneHitEventTimingDistributionGraph.CreateDistributedHitEvents() - }; + var score = TestResources.CreateTestScoreInfo(); + score.HitEvents = TestSceneHitEventTimingDistributionGraph.CreateDistributedHitEvents(); loadPanel(score); } @@ -31,10 +30,8 @@ namespace osu.Game.Tests.Visual.Ranking [Test] public void TestScoreWithPositionStatistics() { - var score = new TestScoreInfo(new OsuRuleset().RulesetInfo) - { - HitEvents = createPositionDistributedHitEvents() - }; + var score = TestResources.CreateTestScoreInfo(); + score.HitEvents = createPositionDistributedHitEvents(); loadPanel(score); } @@ -42,7 +39,7 @@ namespace osu.Game.Tests.Visual.Ranking [Test] public void TestScoreWithoutStatistics() { - loadPanel(new TestScoreInfo(new OsuRuleset().RulesetInfo)); + loadPanel(TestResources.CreateTestScoreInfo(new OsuRuleset().RulesetInfo)); } [Test] diff --git a/osu.Game/Tests/TestScoreInfo.cs b/osu.Game/Tests/TestScoreInfo.cs deleted file mode 100644 index a53cb0ae78..0000000000 --- a/osu.Game/Tests/TestScoreInfo.cs +++ /dev/null @@ -1,66 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using System; -using System.Linq; -using osu.Game.Online.API.Requests.Responses; -using osu.Game.Rulesets; -using osu.Game.Rulesets.Mods; -using osu.Game.Rulesets.Scoring; -using osu.Game.Scoring; -using osu.Game.Tests.Beatmaps; - -namespace osu.Game.Tests -{ - public class TestScoreInfo : ScoreInfo - { - public TestScoreInfo(RulesetInfo ruleset, bool excessMods = false) - { - User = new APIUser - { - Id = 2, - Username = "peppy", - CoverUrl = "https://osu.ppy.sh/images/headers/profile-covers/c3.jpg", - }; - - BeatmapInfo = new TestBeatmap(ruleset).BeatmapInfo; - Ruleset = ruleset; - RulesetID = ruleset.ID ?? 0; - - Mods = excessMods - ? ruleset.CreateInstance().CreateAllMods().ToArray() - : new Mod[] { new TestModHardRock(), new TestModDoubleTime() }; - - TotalScore = 2845370; - Accuracy = 0.95; - MaxCombo = 999; - Rank = ScoreRank.S; - Date = DateTimeOffset.Now; - - Statistics[HitResult.Miss] = 1; - Statistics[HitResult.Meh] = 50; - Statistics[HitResult.Ok] = 100; - Statistics[HitResult.Good] = 200; - Statistics[HitResult.Great] = 300; - Statistics[HitResult.Perfect] = 320; - Statistics[HitResult.SmallTickHit] = 50; - Statistics[HitResult.SmallTickMiss] = 25; - Statistics[HitResult.LargeTickHit] = 100; - Statistics[HitResult.LargeTickMiss] = 50; - Statistics[HitResult.SmallBonus] = 10; - Statistics[HitResult.SmallBonus] = 50; - - Position = 1; - } - - private class TestModHardRock : ModHardRock - { - public override double ScoreMultiplier => 1; - } - - private class TestModDoubleTime : ModDoubleTime - { - public override double ScoreMultiplier => 1; - } - } -} From 99ac71c1fe9b109c064b2deac88aadd10b290544 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 13 Dec 2021 16:37:20 +0900 Subject: [PATCH 090/146] Simplify usages where the ruleset being used is osu! ruleset --- .../TestScenePlaylistsResultsScreen.cs | 6 +++--- .../TestSceneContractedPanelMiddleContent.cs | 4 ++-- .../Visual/Ranking/TestSceneResultsScreen.cs | 9 ++++---- .../Visual/Ranking/TestSceneScorePanelList.cs | 21 +++++++++---------- .../Ranking/TestSceneStatisticsPanel.cs | 3 +-- 5 files changed, 20 insertions(+), 23 deletions(-) diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs index faa0ce2bdc..25ca1299ef 100644 --- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs +++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs @@ -53,7 +53,7 @@ namespace osu.Game.Tests.Visual.Playlists AddStep("bind user score info handler", () => { - userScore = TestResources.CreateTestScoreInfo(new OsuRuleset().RulesetInfo); + userScore = TestResources.CreateTestScoreInfo(); userScore.OnlineID = currentScoreId++; bindHandler(userScore: userScore); @@ -81,7 +81,7 @@ namespace osu.Game.Tests.Visual.Playlists AddStep("bind user score info handler", () => { - userScore = TestResources.CreateTestScoreInfo(new OsuRuleset().RulesetInfo); + userScore = TestResources.CreateTestScoreInfo(); userScore.OnlineID = currentScoreId++; bindHandler(true, userScore); @@ -132,7 +132,7 @@ namespace osu.Game.Tests.Visual.Playlists AddStep("bind user score info handler", () => { - userScore = TestResources.CreateTestScoreInfo(new OsuRuleset().RulesetInfo); + userScore = TestResources.CreateTestScoreInfo(); userScore.OnlineID = currentScoreId++; bindHandler(userScore: userScore); diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneContractedPanelMiddleContent.cs b/osu.Game.Tests/Visual/Ranking/TestSceneContractedPanelMiddleContent.cs index 04ddc5d7f0..a84a7b3993 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneContractedPanelMiddleContent.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneContractedPanelMiddleContent.cs @@ -23,13 +23,13 @@ namespace osu.Game.Tests.Visual.Ranking [Test] public void TestShowPanel() { - AddStep("show example score", () => showPanel(CreateWorkingBeatmap(CreateBeatmap(new OsuRuleset().RulesetInfo)), TestResources.CreateTestScoreInfo(new OsuRuleset().RulesetInfo))); + AddStep("show example score", () => showPanel(CreateWorkingBeatmap(CreateBeatmap(new OsuRuleset().RulesetInfo)), TestResources.CreateTestScoreInfo())); } [Test] public void TestExcessMods() { - AddStep("show excess mods score", () => showPanel(CreateWorkingBeatmap(CreateBeatmap(new OsuRuleset().RulesetInfo)), TestResources.CreateTestScoreInfo(new OsuRuleset().RulesetInfo, true))); + AddStep("show excess mods score", () => showPanel(CreateWorkingBeatmap(CreateBeatmap(new OsuRuleset().RulesetInfo)), TestResources.CreateTestScoreInfo(excessMods: true))); } private void showPanel(WorkingBeatmap workingBeatmap, ScoreInfo score) diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs b/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs index 809f513a83..d0bd5a6e66 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs @@ -15,7 +15,6 @@ using osu.Framework.Utils; using osu.Game.Beatmaps; using osu.Game.Graphics.UserInterface; using osu.Game.Online.API; -using osu.Game.Rulesets.Osu; using osu.Game.Scoring; using osu.Game.Screens; using osu.Game.Screens.Play; @@ -204,7 +203,7 @@ namespace osu.Game.Tests.Visual.Ranking { DelayedFetchResultsScreen screen = null; - AddStep("load results", () => Child = new TestResultsContainer(screen = new DelayedFetchResultsScreen(TestResources.CreateTestScoreInfo(new OsuRuleset().RulesetInfo), 3000))); + AddStep("load results", () => Child = new TestResultsContainer(screen = new DelayedFetchResultsScreen(TestResources.CreateTestScoreInfo(), 3000))); AddUntilStep("wait for loaded", () => screen.IsLoaded); AddStep("click expanded panel", () => { @@ -237,9 +236,9 @@ namespace osu.Game.Tests.Visual.Ranking AddAssert("download button is enabled", () => screen.ChildrenOfType().Last().Enabled.Value); } - private TestResultsScreen createResultsScreen(ScoreInfo score = null) => new TestResultsScreen(score ?? TestResources.CreateTestScoreInfo(new OsuRuleset().RulesetInfo)); + private TestResultsScreen createResultsScreen(ScoreInfo score = null) => new TestResultsScreen(score ?? TestResources.CreateTestScoreInfo()); - private UnrankedSoloResultsScreen createUnrankedSoloResultsScreen() => new UnrankedSoloResultsScreen(TestResources.CreateTestScoreInfo(new OsuRuleset().RulesetInfo)); + private UnrankedSoloResultsScreen createUnrankedSoloResultsScreen() => new UnrankedSoloResultsScreen(TestResources.CreateTestScoreInfo()); private class TestResultsContainer : Container { @@ -282,7 +281,7 @@ namespace osu.Game.Tests.Visual.Ranking for (int i = 0; i < 20; i++) { - var score = TestResources.CreateTestScoreInfo(new OsuRuleset().RulesetInfo); + var score = TestResources.CreateTestScoreInfo(); score.TotalScore += 10 - i; score.Hash = $"test{i}"; scores.Add(score); diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneScorePanelList.cs b/osu.Game.Tests/Visual/Ranking/TestSceneScorePanelList.cs index f963be5d81..f5ad352b9c 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneScorePanelList.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneScorePanelList.cs @@ -7,7 +7,6 @@ using NUnit.Framework; using osu.Framework.Graphics; using osu.Framework.Testing; using osu.Framework.Utils; -using osu.Game.Rulesets.Osu; using osu.Game.Scoring; using osu.Game.Screens.Ranking; using osu.Game.Tests.Resources; @@ -30,14 +29,14 @@ namespace osu.Game.Tests.Visual.Ranking { createListStep(() => new ScorePanelList { - SelectedScore = { Value = TestResources.CreateTestScoreInfo(new OsuRuleset().RulesetInfo) } + SelectedScore = { Value = TestResources.CreateTestScoreInfo() } }); } [Test] public void TestAddPanelAfterSelectingScore() { - var score = TestResources.CreateTestScoreInfo(new OsuRuleset().RulesetInfo); + var score = TestResources.CreateTestScoreInfo(); createListStep(() => new ScorePanelList { @@ -53,7 +52,7 @@ namespace osu.Game.Tests.Visual.Ranking [Test] public void TestAddPanelBeforeSelectingScore() { - var score = TestResources.CreateTestScoreInfo(new OsuRuleset().RulesetInfo); + var score = TestResources.CreateTestScoreInfo(); createListStep(() => new ScorePanelList()); @@ -76,7 +75,7 @@ namespace osu.Game.Tests.Visual.Ranking AddStep("add many scores", () => { for (int i = 0; i < 20; i++) - list.AddScore(TestResources.CreateTestScoreInfo(new OsuRuleset().RulesetInfo)); + list.AddScore(TestResources.CreateTestScoreInfo()); }); assertFirstPanelCentred(); @@ -85,7 +84,7 @@ namespace osu.Game.Tests.Visual.Ranking [Test] public void TestAddManyScoresAfterExpandedPanel() { - var initialScore = TestResources.CreateTestScoreInfo(new OsuRuleset().RulesetInfo); + var initialScore = TestResources.CreateTestScoreInfo(); createListStep(() => new ScorePanelList()); @@ -108,7 +107,7 @@ namespace osu.Game.Tests.Visual.Ranking [Test] public void TestAddManyScoresBeforeExpandedPanel() { - var initialScore = TestResources.CreateTestScoreInfo(new OsuRuleset().RulesetInfo); + var initialScore = TestResources.CreateTestScoreInfo(); createListStep(() => new ScorePanelList()); @@ -131,7 +130,7 @@ namespace osu.Game.Tests.Visual.Ranking [Test] public void TestAddManyPanelsOnBothSidesOfExpandedPanel() { - var initialScore = TestResources.CreateTestScoreInfo(new OsuRuleset().RulesetInfo); + var initialScore = TestResources.CreateTestScoreInfo(); createListStep(() => new ScorePanelList()); @@ -157,8 +156,8 @@ namespace osu.Game.Tests.Visual.Ranking [Test] public void TestSelectMultipleScores() { - var firstScore = TestResources.CreateTestScoreInfo(new OsuRuleset().RulesetInfo); - var secondScore = TestResources.CreateTestScoreInfo(new OsuRuleset().RulesetInfo); + var firstScore = TestResources.CreateTestScoreInfo(); + var secondScore = TestResources.CreateTestScoreInfo(); firstScore.UserString = "A"; secondScore.UserString = "B"; @@ -191,7 +190,7 @@ namespace osu.Game.Tests.Visual.Ranking [Test] public void TestAddScoreImmediately() { - var score = TestResources.CreateTestScoreInfo(new OsuRuleset().RulesetInfo); + var score = TestResources.CreateTestScoreInfo(); createListStep(() => { diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneStatisticsPanel.cs b/osu.Game.Tests/Visual/Ranking/TestSceneStatisticsPanel.cs index ebea523b9e..f64b7b2b65 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneStatisticsPanel.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneStatisticsPanel.cs @@ -6,7 +6,6 @@ using System.Collections.Generic; using NUnit.Framework; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Game.Rulesets.Osu; using osu.Game.Scoring; using osu.Game.Screens.Ranking.Statistics; using osu.Game.Rulesets.Osu.Objects; @@ -39,7 +38,7 @@ namespace osu.Game.Tests.Visual.Ranking [Test] public void TestScoreWithoutStatistics() { - loadPanel(TestResources.CreateTestScoreInfo(new OsuRuleset().RulesetInfo)); + loadPanel(TestResources.CreateTestScoreInfo()); } [Test] From 654b47c7ecd67272038672a9f468059f667b0247 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 13 Dec 2021 16:41:29 +0900 Subject: [PATCH 091/146] Move "excess mods" test behaviour to local usages There were only two of these, so it doesn't make sense to add extra complexity in the test resources class. --- osu.Game.Tests/Resources/TestResources.cs | 12 ++++-------- .../Ranking/TestSceneContractedPanelMiddleContent.cs | 8 +++++++- .../Ranking/TestSceneExpandedPanelMiddleContent.cs | 10 ++++++++-- 3 files changed, 19 insertions(+), 11 deletions(-) diff --git a/osu.Game.Tests/Resources/TestResources.cs b/osu.Game.Tests/Resources/TestResources.cs index 7535f6769f..445394fc77 100644 --- a/osu.Game.Tests/Resources/TestResources.cs +++ b/osu.Game.Tests/Resources/TestResources.cs @@ -147,18 +147,16 @@ namespace osu.Game.Tests.Resources /// Create a test score model. /// /// The ruleset for which the score was set against. - /// Whether to include an excessive number of mods. If false, only two will be added. /// - public static ScoreInfo CreateTestScoreInfo(RulesetInfo ruleset = null, bool excessMods = false) => - CreateTestScoreInfo(CreateTestBeatmapSetInfo(1, new[] { ruleset ?? new OsuRuleset().RulesetInfo }).Beatmaps.First(), excessMods); + public static ScoreInfo CreateTestScoreInfo(RulesetInfo ruleset = null) => + CreateTestScoreInfo(CreateTestBeatmapSetInfo(1, new[] { ruleset ?? new OsuRuleset().RulesetInfo }).Beatmaps.First()); /// /// Create a test score model. /// /// The beatmap for which the score was set against. - /// Whether to include an excessive number of mods. If false, only two will be added. /// - public static ScoreInfo CreateTestScoreInfo(BeatmapInfo beatmap, bool excessMods = false) => new ScoreInfo + public static ScoreInfo CreateTestScoreInfo(BeatmapInfo beatmap) => new ScoreInfo { User = new APIUser { @@ -169,9 +167,7 @@ namespace osu.Game.Tests.Resources BeatmapInfo = beatmap, Ruleset = beatmap.Ruleset, RulesetID = beatmap.Ruleset.ID ?? 0, - Mods = excessMods - ? beatmap.Ruleset.CreateInstance().CreateAllMods().ToArray() - : new Mod[] { new TestModHardRock(), new TestModDoubleTime() }, + Mods = new Mod[] { new TestModHardRock(), new TestModDoubleTime() }, TotalScore = 2845370, Accuracy = 0.95, MaxCombo = 999, diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneContractedPanelMiddleContent.cs b/osu.Game.Tests/Visual/Ranking/TestSceneContractedPanelMiddleContent.cs index a84a7b3993..85306b9354 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneContractedPanelMiddleContent.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneContractedPanelMiddleContent.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -29,7 +30,12 @@ namespace osu.Game.Tests.Visual.Ranking [Test] public void TestExcessMods() { - AddStep("show excess mods score", () => showPanel(CreateWorkingBeatmap(CreateBeatmap(new OsuRuleset().RulesetInfo)), TestResources.CreateTestScoreInfo(excessMods: true))); + AddStep("show excess mods score", () => + { + var score = TestResources.CreateTestScoreInfo(); + score.Mods = score.BeatmapInfo.Ruleset.CreateInstance().CreateAllMods().ToArray(); + showPanel(CreateWorkingBeatmap(CreateBeatmap(new OsuRuleset().RulesetInfo)), score); + }); } private void showPanel(WorkingBeatmap workingBeatmap, ScoreInfo score) diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneExpandedPanelMiddleContent.cs b/osu.Game.Tests/Visual/Ranking/TestSceneExpandedPanelMiddleContent.cs index 1f34606fcb..2cb4fb6b6b 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneExpandedPanelMiddleContent.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneExpandedPanelMiddleContent.cs @@ -41,9 +41,15 @@ namespace osu.Game.Tests.Visual.Ranking [Test] public void TestExcessMods() { - var author = new APIUser { Username = "mapper_name" }; + AddStep("show excess mods score", () => + { + var author = new APIUser { Username = "mapper_name" }; - AddStep("show excess mods score", () => showPanel(TestResources.CreateTestScoreInfo(createTestBeatmap(author), true))); + var score = TestResources.CreateTestScoreInfo(createTestBeatmap(author)); + score.Mods = score.BeatmapInfo.Ruleset.CreateInstance().CreateAllMods().ToArray(); + + showPanel(score); + }); AddAssert("mapper name present", () => this.ChildrenOfType().Any(spriteText => spriteText.Current.Value == "mapper_name")); } From 309290a3c9b92e40d88328720b23e544de7c892b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 13 Dec 2021 16:50:29 +0900 Subject: [PATCH 092/146] Use new method in more places that can benefit from it --- .../Online/TestAPIModJsonSerialization.cs | 3 +-- .../Background/TestSceneUserDimBackgrounds.cs | 9 +-------- .../Visual/Gameplay/TestSceneFailJudgement.cs | 1 + .../TestSceneMultiplayerResults.cs | 19 ++----------------- .../TestSceneMultiplayerTeamResults.cs | 19 ++----------------- .../SongSelect/TestScenePlaySongSelect.cs | 7 +------ 6 files changed, 8 insertions(+), 50 deletions(-) diff --git a/osu.Game.Tests/Online/TestAPIModJsonSerialization.cs b/osu.Game.Tests/Online/TestAPIModJsonSerialization.cs index 8378b33b3d..8b8954a1d0 100644 --- a/osu.Game.Tests/Online/TestAPIModJsonSerialization.cs +++ b/osu.Game.Tests/Online/TestAPIModJsonSerialization.cs @@ -94,7 +94,7 @@ namespace osu.Game.Tests.Online [Test] public void TestDeserialiseSubmittableScoreWithEmptyMods() { - var score = new SubmittableScore(new ScoreInfo { Ruleset = new OsuRuleset().RulesetInfo }); + var score = new SubmittableScore(new ScoreInfo()); var deserialised = JsonConvert.DeserializeObject(JsonConvert.SerializeObject(score)); @@ -106,7 +106,6 @@ namespace osu.Game.Tests.Online { var score = new SubmittableScore(new ScoreInfo { - Ruleset = new OsuRuleset().RulesetInfo, Mods = new Mod[] { new OsuModDoubleTime { SpeedChange = { Value = 2 } } } }); diff --git a/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs b/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs index 194341d1ab..33b1d9a67d 100644 --- a/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs +++ b/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs @@ -18,7 +18,6 @@ using osu.Game.Configuration; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; -using osu.Game.Online.API.Requests.Responses; using osu.Game.Rulesets; using osu.Game.Rulesets.Osu.Mods; using osu.Game.Scoring; @@ -28,7 +27,6 @@ using osu.Game.Screens.Play; using osu.Game.Screens.Play.PlayerSettings; using osu.Game.Screens.Ranking; using osu.Game.Screens.Select; -using osu.Game.Tests.Beatmaps; using osu.Game.Tests.Resources; using osuTK; using osuTK.Graphics; @@ -229,12 +227,7 @@ namespace osu.Game.Tests.Visual.Background FadeAccessibleResults results = null; - AddStep("Transition to Results", () => player.Push(results = new FadeAccessibleResults(new ScoreInfo - { - User = new APIUser { Username = "osu!" }, - BeatmapInfo = new TestBeatmap(Ruleset.Value).BeatmapInfo, - Ruleset = Ruleset.Value, - }))); + AddStep("Transition to Results", () => player.Push(results = new FadeAccessibleResults(TestResources.CreateTestScoreInfo()))); AddUntilStep("Wait for results is current", () => results.IsCurrentScreen()); diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneFailJudgement.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneFailJudgement.cs index 745932315c..fa27e1abdd 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneFailJudgement.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneFailJudgement.cs @@ -26,6 +26,7 @@ namespace osu.Game.Tests.Visual.Gameplay AddAssert("total number of results == 1", () => { var score = new ScoreInfo(); + ((FailPlayer)Player).ScoreProcessor.PopulateScore(score); return score.Statistics.Values.Sum() == 1; diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerResults.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerResults.cs index 744a2d187d..4674601f28 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerResults.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerResults.cs @@ -1,13 +1,11 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; using NUnit.Framework; -using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.Rooms; using osu.Game.Rulesets.Osu; -using osu.Game.Scoring; using osu.Game.Screens.OnlinePlay.Multiplayer; +using osu.Game.Tests.Resources; namespace osu.Game.Tests.Visual.Multiplayer { @@ -22,20 +20,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { var rulesetInfo = new OsuRuleset().RulesetInfo; var beatmapInfo = CreateBeatmap(rulesetInfo).BeatmapInfo; - - var score = new ScoreInfo - { - Rank = ScoreRank.B, - TotalScore = 987654, - Accuracy = 0.8, - MaxCombo = 500, - Combo = 250, - BeatmapInfo = beatmapInfo, - User = new APIUser { Username = "Test user" }, - Date = DateTimeOffset.Now, - OnlineID = 12345, - Ruleset = rulesetInfo, - }; + var score = TestResources.CreateTestScoreInfo(beatmapInfo); PlaylistItem playlistItem = new PlaylistItem { diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerTeamResults.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerTeamResults.cs index 99b6edc3b6..f5df8d7507 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerTeamResults.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerTeamResults.cs @@ -1,15 +1,13 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; using System.Collections.Generic; using NUnit.Framework; using osu.Framework.Bindables; -using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.Rooms; using osu.Game.Rulesets.Osu; -using osu.Game.Scoring; using osu.Game.Screens.OnlinePlay.Multiplayer; +using osu.Game.Tests.Resources; namespace osu.Game.Tests.Visual.Multiplayer { @@ -26,20 +24,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { var rulesetInfo = new OsuRuleset().RulesetInfo; var beatmapInfo = CreateBeatmap(rulesetInfo).BeatmapInfo; - - var score = new ScoreInfo - { - Rank = ScoreRank.B, - TotalScore = 987654, - Accuracy = 0.8, - MaxCombo = 500, - Combo = 250, - BeatmapInfo = beatmapInfo, - User = new APIUser { Username = "Test user" }, - Date = DateTimeOffset.Now, - OnlineID = 12345, - Ruleset = rulesetInfo, - }; + var score = TestResources.CreateTestScoreInfo(beatmapInfo); PlaylistItem playlistItem = new PlaylistItem { diff --git a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs index 0494d1de3c..be390742ea 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs @@ -835,12 +835,7 @@ namespace osu.Game.Tests.Visual.SongSelect // this beatmap change should be overridden by the present. Beatmap.Value = manager.GetWorkingBeatmap(getSwitchBeatmap()); - songSelect.PresentScore(new ScoreInfo - { - User = new APIUser { Username = "woo" }, - BeatmapInfo = getPresentBeatmap(), - Ruleset = getPresentBeatmap().Ruleset - }); + songSelect.PresentScore(TestResources.CreateTestScoreInfo(getPresentBeatmap())); }); AddUntilStep("wait for results screen presented", () => !songSelect.IsCurrentScreen()); From 9e9341597d3ba0ecc05b36f88fbf8dcad59f1d1b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 13 Dec 2021 17:59:04 +0900 Subject: [PATCH 093/146] Remove unused using statement --- osu.Game.Tests/Online/TestAPIModJsonSerialization.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Tests/Online/TestAPIModJsonSerialization.cs b/osu.Game.Tests/Online/TestAPIModJsonSerialization.cs index 8b8954a1d0..4b160e1d67 100644 --- a/osu.Game.Tests/Online/TestAPIModJsonSerialization.cs +++ b/osu.Game.Tests/Online/TestAPIModJsonSerialization.cs @@ -13,7 +13,6 @@ using osu.Game.Online.Solo; using osu.Game.Rulesets; using osu.Game.Rulesets.Difficulty; using osu.Game.Rulesets.Mods; -using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Mods; using osu.Game.Rulesets.UI; using osu.Game.Scoring; From c87ff82c1cde3af45c173fcb264de999340b743c Mon Sep 17 00:00:00 2001 From: rumoi Date: Tue, 14 Dec 2021 09:25:29 +1300 Subject: [PATCH 094/146] calculateRhythmBonus performance fix. --- osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs index d00d89a577..30d989cfba 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs @@ -55,7 +55,12 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills bool firstDeltaSwitch = false; - for (int i = Previous.Count - 2; i > 0; i--) + int rhythmStart = Math.Min(Previous.Count - 2, 0); + + while (rhythmStart < Previous.Count - 2 && current.StartTime - Previous[rhythmStart].StartTime < history_time_max) + rhythmStart++; + + for (int i = rhythmStart; i > 0; i--) { OsuDifficultyHitObject currObj = (OsuDifficultyHitObject)Previous[i - 1]; OsuDifficultyHitObject prevObj = (OsuDifficultyHitObject)Previous[i]; From 6e3558b2226d834bd57146dec9bcd197ab441c30 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 14 Dec 2021 06:38:57 +0900 Subject: [PATCH 095/146] Remove weird test --- .../TestScenePlaylistsRoomCreation.cs | 36 ------------------- 1 file changed, 36 deletions(-) diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomCreation.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomCreation.cs index c5287d4257..450e821ba6 100644 --- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomCreation.cs +++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomCreation.cs @@ -109,42 +109,6 @@ namespace osu.Game.Tests.Visual.Playlists AddAssert("first playlist item selected", () => match.SelectedItem.Value == SelectedRoom.Value.Playlist[0]); } - [Test] - public void TestBeatmapUpdatedOnReImport() - { - BeatmapSetInfo importedSet = null; - - AddStep("import altered beatmap", () => - { - IBeatmap beatmap = CreateBeatmap(new OsuRuleset().RulesetInfo); - - beatmap.BeatmapInfo.BaseDifficulty.CircleSize = 1; - - // intentionally increment online IDs to clash with import below. - beatmap.BeatmapInfo.OnlineID++; - beatmap.BeatmapInfo.BeatmapSet.OnlineID++; - - importedSet = manager.Import(beatmap.BeatmapInfo.BeatmapSet).Result.Value; - }); - - setupAndCreateRoom(room => - { - room.Name.Value = "my awesome room"; - room.Host.Value = API.LocalUser.Value; - room.Playlist.Add(new PlaylistItem - { - Beatmap = { Value = importedSet.Beatmaps[0] }, - Ruleset = { Value = new OsuRuleset().RulesetInfo } - }); - }); - - AddAssert("match has altered beatmap", () => match.Beatmap.Value.Beatmap.Difficulty.CircleSize == 1); - - importBeatmap(); - - AddAssert("match has original beatmap", () => match.Beatmap.Value.Beatmap.Difficulty.CircleSize != 1); - } - private void setupAndCreateRoom(Action room) { AddStep("setup room", () => room(SelectedRoom.Value)); From 7564658b5e01c780feadd62dccabbf1f8a051d31 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 14 Dec 2021 06:40:45 +0900 Subject: [PATCH 096/146] Reduce to 30s --- osu.Game/Online/Rooms/SubmitRoomScoreRequest.cs | 2 +- osu.Game/Online/Solo/SubmitSoloScoreRequest.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Online/Rooms/SubmitRoomScoreRequest.cs b/osu.Game/Online/Rooms/SubmitRoomScoreRequest.cs index 4ee4be6164..e24d113822 100644 --- a/osu.Game/Online/Rooms/SubmitRoomScoreRequest.cs +++ b/osu.Game/Online/Rooms/SubmitRoomScoreRequest.cs @@ -31,7 +31,7 @@ namespace osu.Game.Online.Rooms req.ContentType = "application/json"; req.Method = HttpMethod.Put; - req.Timeout = 60000; + req.Timeout = 30000; req.AddRaw(JsonConvert.SerializeObject(score, new JsonSerializerSettings { diff --git a/osu.Game/Online/Solo/SubmitSoloScoreRequest.cs b/osu.Game/Online/Solo/SubmitSoloScoreRequest.cs index 763fcf3f20..99cf5ceff5 100644 --- a/osu.Game/Online/Solo/SubmitSoloScoreRequest.cs +++ b/osu.Game/Online/Solo/SubmitSoloScoreRequest.cs @@ -31,7 +31,7 @@ namespace osu.Game.Online.Solo req.ContentType = "application/json"; req.Method = HttpMethod.Put; - req.Timeout = 60000; + req.Timeout = 30000; req.AddRaw(JsonConvert.SerializeObject(score, new JsonSerializerSettings { From 2f1dc912117f91443d912961e3f16df71ee11529 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 14 Dec 2021 11:30:42 +0900 Subject: [PATCH 097/146] Add AbortLoad() method to abort gameplay loads --- .../Multiplayer/IMultiplayerRoomServer.cs | 5 +++++ .../Online/Multiplayer/MultiplayerClient.cs | 2 ++ .../Multiplayer/OnlineMultiplayerClient.cs | 8 +++++++ .../OnlinePlay/Multiplayer/Multiplayer.cs | 21 +++++++++++++++++-- .../Multiplayer/TestMultiplayerClient.cs | 15 +++++++++++++ 5 files changed, 49 insertions(+), 2 deletions(-) diff --git a/osu.Game/Online/Multiplayer/IMultiplayerRoomServer.cs b/osu.Game/Online/Multiplayer/IMultiplayerRoomServer.cs index 73fda78d00..200539def7 100644 --- a/osu.Game/Online/Multiplayer/IMultiplayerRoomServer.cs +++ b/osu.Game/Online/Multiplayer/IMultiplayerRoomServer.cs @@ -77,6 +77,11 @@ namespace osu.Game.Online.Multiplayer /// If an attempt to start the game occurs when the game's (or users') state disallows it. Task StartMatch(); + /// + /// Aborts an ongoing gameplay load. + /// + Task AbortLoad(); + /// /// Adds an item to the playlist. /// diff --git a/osu.Game/Online/Multiplayer/MultiplayerClient.cs b/osu.Game/Online/Multiplayer/MultiplayerClient.cs index 55b4def908..78d8362170 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerClient.cs @@ -333,6 +333,8 @@ namespace osu.Game.Online.Multiplayer public abstract Task StartMatch(); + public abstract Task AbortLoad(); + public abstract Task AddPlaylistItem(MultiplayerPlaylistItem item); public abstract Task EditPlaylistItem(MultiplayerPlaylistItem item); diff --git a/osu.Game/Online/Multiplayer/OnlineMultiplayerClient.cs b/osu.Game/Online/Multiplayer/OnlineMultiplayerClient.cs index d268d2bf69..3062cf8b99 100644 --- a/osu.Game/Online/Multiplayer/OnlineMultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/OnlineMultiplayerClient.cs @@ -154,6 +154,14 @@ namespace osu.Game.Online.Multiplayer return connection.InvokeAsync(nameof(IMultiplayerServer.StartMatch)); } + public override Task AbortLoad() + { + if (!IsConnected.Value) + return Task.CompletedTask; + + return connection.InvokeAsync(nameof(IMultiplayerServer.AbortLoad)); + } + public override Task AddPlaylistItem(MultiplayerPlaylistItem item) { if (!IsConnected.Value) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Multiplayer.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Multiplayer.cs index 58b5b7bbeb..c299fd285a 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Multiplayer.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Multiplayer.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Diagnostics; using osu.Framework.Allocation; using osu.Framework.Screens; using osu.Game.Online.Multiplayer; @@ -18,8 +19,24 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer { base.OnResuming(last); - if (client.Room != null && client.LocalUser?.State != MultiplayerUserState.Spectating) - client.ChangeState(MultiplayerUserState.Idle); + if (client.Room == null) + return; + + Debug.Assert(client.LocalUser != null); + + switch (client.LocalUser.State) + { + case MultiplayerUserState.Spectating: + break; + + case MultiplayerUserState.WaitingForLoad: + client.AbortLoad(); + break; + + default: + client.ChangeState(MultiplayerUserState.Idle); + break; + } } protected override string ScreenTitle => "Multiplayer"; diff --git a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs index d22f0415e6..c928cfd137 100644 --- a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs +++ b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs @@ -242,6 +242,11 @@ namespace osu.Game.Tests.Visual.Multiplayer public override Task ChangeState(MultiplayerUserState newState) { + Debug.Assert(Room != null); + + if (newState == MultiplayerUserState.Idle && LocalUser?.State == MultiplayerUserState.WaitingForLoad) + return Task.CompletedTask; + ChangeUserState(api.LocalUser.Value.Id, newState); return Task.CompletedTask; } @@ -303,6 +308,16 @@ namespace osu.Game.Tests.Visual.Multiplayer return ((IMultiplayerClient)this).LoadRequested(); } + public override Task AbortLoad() + { + Debug.Assert(Room != null); + Debug.Assert(LocalUser != null); + + ChangeUserState(LocalUser.UserID, MultiplayerUserState.Idle); + + return Task.CompletedTask; + } + public async Task AddUserPlaylistItem(int userId, MultiplayerPlaylistItem item) { Debug.Assert(Room != null); From 750bfae9092095881432f0ab35eee1bbcbf068a5 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 14 Dec 2021 11:35:56 +0900 Subject: [PATCH 098/146] Fix TestMultiplayerClient not handling all users bailing from gameplay --- .../Tests/Visual/Multiplayer/TestMultiplayerClient.cs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs index c928cfd137..4c69f8f9d2 100644 --- a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs +++ b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs @@ -128,6 +128,15 @@ namespace osu.Game.Tests.Visual.Multiplayer case MultiplayerRoomState.WaitingForLoad: if (Room.Users.All(u => u.State != MultiplayerUserState.WaitingForLoad)) { + var loadedUsers = Room.Users.Where(u => u.State == MultiplayerUserState.Loaded).ToArray(); + + if (loadedUsers.Length == 0) + { + // all users have bailed from the load sequence. cancel the game start. + ChangeRoomState(MultiplayerRoomState.Open); + return; + } + foreach (var u in Room.Users.Where(u => u.State == MultiplayerUserState.Loaded)) ChangeUserState(u.UserID, MultiplayerUserState.Playing); @@ -143,8 +152,8 @@ namespace osu.Game.Tests.Visual.Multiplayer { foreach (var u in Room.Users.Where(u => u.State == MultiplayerUserState.FinishedPlay)) ChangeUserState(u.UserID, MultiplayerUserState.Results); - ChangeRoomState(MultiplayerRoomState.Open); + ChangeRoomState(MultiplayerRoomState.Open); ((IMultiplayerClient)this).ResultsReady(); FinishCurrentItem().Wait(); From 357a6613790bf82f6bb1fccd0742317bcbd96e70 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 14 Dec 2021 13:13:12 +0900 Subject: [PATCH 099/146] Fix storyboard sprites sometimes starting too early --- osu.Game/Storyboards/StoryboardSprite.cs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/osu.Game/Storyboards/StoryboardSprite.cs b/osu.Game/Storyboards/StoryboardSprite.cs index 6fb2f5994b..f941cec20c 100644 --- a/osu.Game/Storyboards/StoryboardSprite.cs +++ b/osu.Game/Storyboards/StoryboardSprite.cs @@ -33,10 +33,8 @@ namespace osu.Game.Storyboards foreach (var l in loops) { - if (!(l.EarliestDisplayedTime is double lEarliest)) - continue; - - earliestStartTime = Math.Min(earliestStartTime, lEarliest); + if (l.EarliestDisplayedTime != null) + earliestStartTime = Math.Min(earliestStartTime, l.StartTime); } if (earliestStartTime < double.MaxValue) From 7e576ae9d3adc3133c03437e705a9a8879997205 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 14 Dec 2021 13:25:19 +0900 Subject: [PATCH 100/146] Add note about how the background colour is restored --- osu.Game/Screens/Play/FailAnimation.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Screens/Play/FailAnimation.cs b/osu.Game/Screens/Play/FailAnimation.cs index aa777f18a8..cfbfdc9966 100644 --- a/osu.Game/Screens/Play/FailAnimation.cs +++ b/osu.Game/Screens/Play/FailAnimation.cs @@ -145,6 +145,7 @@ namespace osu.Game.Screens.Play Content.RotateTo(1, duration, Easing.OutQuart); Content.FadeColour(Color4.Gray, duration); + // Will be restored by `ApplyToBackground` logic in `SongSelect`. Background?.FadeColour(OsuColour.Gray(0.3f), 60); } From 8e6c7eb030fa523a6fe64273e4480b24fa6e49bc Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 14 Dec 2021 13:52:27 +0900 Subject: [PATCH 101/146] Use `OsuStorage` in realm tests to allow for migration Also changes the realm filename to use `client` to match the ignore rules in `OsuStorage`. Without doing this, migration will fail in an indefinite mutex wait when attempting to delete the realm `.note` file. --- osu.Game.Tests/Database/RealmTest.cs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Database/RealmTest.cs b/osu.Game.Tests/Database/RealmTest.cs index 04c9f2577a..6904464485 100644 --- a/osu.Game.Tests/Database/RealmTest.cs +++ b/osu.Game.Tests/Database/RealmTest.cs @@ -10,6 +10,7 @@ using osu.Framework.Logging; using osu.Framework.Platform; using osu.Framework.Testing; using osu.Game.Database; +using osu.Game.IO; using osu.Game.Models; #nullable enable @@ -27,15 +28,16 @@ namespace osu.Game.Tests.Database storage.DeleteDirectory(string.Empty); } - protected void RunTestWithRealm(Action testAction, [CallerMemberName] string caller = "") + protected void RunTestWithRealm(Action testAction, [CallerMemberName] string caller = "") { using (HeadlessGameHost host = new CleanRunHeadlessGameHost(caller)) { host.Run(new RealmTestGame(() => { - var testStorage = storage.GetStorageForDirectory(caller); + // ReSharper disable once AccessToDisposedClosure + var testStorage = new OsuStorage(host, storage.GetStorageForDirectory(caller)); - using (var realmFactory = new RealmContextFactory(testStorage, caller)) + using (var realmFactory = new RealmContextFactory(testStorage, "client")) { Logger.Log($"Running test using realm file {testStorage.GetFullPath(realmFactory.Filename)}"); testAction(realmFactory, testStorage); @@ -58,7 +60,7 @@ namespace osu.Game.Tests.Database { var testStorage = storage.GetStorageForDirectory(caller); - using (var realmFactory = new RealmContextFactory(testStorage, caller)) + using (var realmFactory = new RealmContextFactory(testStorage, "client")) { Logger.Log($"Running test using realm file {testStorage.GetFullPath(realmFactory.Filename)}"); await testAction(realmFactory, testStorage); From be337b4acea61c1bfea6b05442ad5d0201d63d3d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 14 Dec 2021 13:53:23 +0900 Subject: [PATCH 102/146] Add failing test coverage of `RealmLive` failing post storage migration --- osu.Game.Tests/Database/RealmLiveTests.cs | 28 +++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/osu.Game.Tests/Database/RealmLiveTests.cs b/osu.Game.Tests/Database/RealmLiveTests.cs index 9b6769b788..1d197791a4 100644 --- a/osu.Game.Tests/Database/RealmLiveTests.cs +++ b/osu.Game.Tests/Database/RealmLiveTests.cs @@ -6,6 +6,7 @@ using System.Diagnostics; using System.Linq; using System.Threading.Tasks; using NUnit.Framework; +using osu.Framework.Testing; using osu.Game.Database; using osu.Game.Models; using Realms; @@ -29,6 +30,33 @@ namespace osu.Game.Tests.Database }); } + [Test] + public void TestAccessAfterStorageMigrate() + { + RunTestWithRealm((realmFactory, storage) => + { + var beatmap = new RealmBeatmap(CreateRuleset(), new RealmBeatmapDifficulty(), new RealmBeatmapMetadata()); + + ILive liveBeatmap; + + using (var context = realmFactory.CreateContext()) + { + context.Write(r => r.Add(beatmap)); + + liveBeatmap = beatmap.ToLive(); + } + + using (var migratedStorage = new TemporaryNativeStorage("realm-test-migration-target")) + { + migratedStorage.DeleteDirectory(string.Empty); + + storage.Migrate(migratedStorage); + + Assert.IsFalse(liveBeatmap.PerformRead(l => l.Hidden)); + } + }); + } + [Test] public void TestAccessAfterAttach() { From f9a2db5ec6dbf7e87b0cf2c578d7f81be0aa0138 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 14 Dec 2021 14:19:43 +0900 Subject: [PATCH 103/146] Add accessibility to realm factory via `IStorageResourceProvider` We might need to rename this class.. --- osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs | 2 ++ osu.Game/Beatmaps/WorkingBeatmapCache.cs | 2 ++ osu.Game/IO/IStorageResourceProvider.cs | 6 ++++++ osu.Game/Tests/Beatmaps/HitObjectSampleTest.cs | 2 ++ osu.Game/Tests/Visual/SkinnableTestScene.cs | 2 ++ 5 files changed, 14 insertions(+) diff --git a/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs b/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs index 88f35976ad..3aab28886e 100644 --- a/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs +++ b/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs @@ -15,6 +15,7 @@ using osu.Framework.IO.Stores; using osu.Framework.Testing; using osu.Framework.Utils; using osu.Game.Audio; +using osu.Game.Database; using osu.Game.IO; using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; @@ -220,6 +221,7 @@ namespace osu.Game.Tests.Gameplay public AudioManager AudioManager => Audio; public IResourceStore Files => null; public new IResourceStore Resources => base.Resources; + public RealmContextFactory RealmContextFactory => null; public IResourceStore CreateTextureLoaderStore(IResourceStore underlyingStore) => null; #endregion diff --git a/osu.Game/Beatmaps/WorkingBeatmapCache.cs b/osu.Game/Beatmaps/WorkingBeatmapCache.cs index 559685d3c4..449406eadf 100644 --- a/osu.Game/Beatmaps/WorkingBeatmapCache.cs +++ b/osu.Game/Beatmaps/WorkingBeatmapCache.cs @@ -15,6 +15,7 @@ using osu.Framework.Platform; using osu.Framework.Statistics; using osu.Framework.Testing; using osu.Game.Beatmaps.Formats; +using osu.Game.Database; using osu.Game.IO; using osu.Game.Skinning; using osu.Game.Storyboards; @@ -108,6 +109,7 @@ namespace osu.Game.Beatmaps TextureStore IBeatmapResourceProvider.LargeTextureStore => largeTextureStore; ITrackStore IBeatmapResourceProvider.Tracks => trackStore; AudioManager IStorageResourceProvider.AudioManager => audioManager; + RealmContextFactory IStorageResourceProvider.RealmContextFactory => null; IResourceStore IStorageResourceProvider.Files => files; IResourceStore IStorageResourceProvider.Resources => resources; IResourceStore IStorageResourceProvider.CreateTextureLoaderStore(IResourceStore underlyingStore) => host?.CreateTextureLoaderStore(underlyingStore); diff --git a/osu.Game/IO/IStorageResourceProvider.cs b/osu.Game/IO/IStorageResourceProvider.cs index e4c97e18fa..950b5aae09 100644 --- a/osu.Game/IO/IStorageResourceProvider.cs +++ b/osu.Game/IO/IStorageResourceProvider.cs @@ -4,6 +4,7 @@ using osu.Framework.Audio; using osu.Framework.Graphics.Textures; using osu.Framework.IO.Stores; +using osu.Game.Database; namespace osu.Game.IO { @@ -24,6 +25,11 @@ namespace osu.Game.IO /// IResourceStore Resources { get; } + /// + /// Access realm. + /// + RealmContextFactory RealmContextFactory { get; } + /// /// Create a texture loader store based on an underlying data store. /// diff --git a/osu.Game/Tests/Beatmaps/HitObjectSampleTest.cs b/osu.Game/Tests/Beatmaps/HitObjectSampleTest.cs index 31a2071249..f919edecf7 100644 --- a/osu.Game/Tests/Beatmaps/HitObjectSampleTest.cs +++ b/osu.Game/Tests/Beatmaps/HitObjectSampleTest.cs @@ -14,6 +14,7 @@ using osu.Framework.Testing; using osu.Framework.Timing; using osu.Game.Beatmaps; using osu.Game.Beatmaps.Formats; +using osu.Game.Database; using osu.Game.IO; using osu.Game.Models; using osu.Game.Rulesets; @@ -118,6 +119,7 @@ namespace osu.Game.Tests.Beatmaps public IResourceStore Files => userSkinResourceStore; public new IResourceStore Resources => base.Resources; public IResourceStore CreateTextureLoaderStore(IResourceStore underlyingStore) => null; + RealmContextFactory IStorageResourceProvider.RealmContextFactory => null; #endregion diff --git a/osu.Game/Tests/Visual/SkinnableTestScene.cs b/osu.Game/Tests/Visual/SkinnableTestScene.cs index cdd3e47930..22aac79056 100644 --- a/osu.Game/Tests/Visual/SkinnableTestScene.cs +++ b/osu.Game/Tests/Visual/SkinnableTestScene.cs @@ -15,6 +15,7 @@ using osu.Framework.Graphics.Textures; using osu.Framework.IO.Stores; using osu.Framework.Platform; using osu.Game.Beatmaps; +using osu.Game.Database; using osu.Game.Graphics.Sprites; using osu.Game.IO; using osu.Game.Rulesets; @@ -158,6 +159,7 @@ namespace osu.Game.Tests.Visual public IResourceStore Files => null; public new IResourceStore Resources => base.Resources; public IResourceStore CreateTextureLoaderStore(IResourceStore underlyingStore) => host.CreateTextureLoaderStore(underlyingStore); + RealmContextFactory IStorageResourceProvider.RealmContextFactory => null; #endregion From 441b7baa93225658fd82ed78420fd597bcd8bb62 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 14 Dec 2021 14:21:23 +0900 Subject: [PATCH 104/146] Provide a realm factory to usages of `ToLive`/`RealmLive` --- .../Editor/TestSceneManiaComposeScreen.cs | 4 +-- osu.Game.Tests/Database/RealmLiveTests.cs | 22 +++++++------- .../TestSceneBackgroundScreenDefault.cs | 2 +- .../Gameplay/TestSceneBeatmapSkinFallbacks.cs | 2 +- osu.Game/Database/RealmLive.cs | 30 ++++++++++++------- osu.Game/Database/RealmObjectExtensions.cs | 20 ++++++++++--- osu.Game/OsuGame.cs | 4 +-- .../Overlays/Settings/Sections/SkinSection.cs | 6 ++-- osu.Game/Skinning/Skin.cs | 2 +- osu.Game/Skinning/SkinManager.cs | 13 ++++---- osu.Game/Stores/RealmArchiveModelImporter.cs | 6 ++-- 11 files changed, 67 insertions(+), 44 deletions(-) diff --git a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaComposeScreen.cs b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaComposeScreen.cs index 91f5f93905..a30e09cd29 100644 --- a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaComposeScreen.cs +++ b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaComposeScreen.cs @@ -56,13 +56,13 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor [Test] public void TestDefaultSkin() { - AddStep("set default skin", () => skins.CurrentSkinInfo.Value = DefaultSkin.CreateInfo().ToLive()); + AddStep("set default skin", () => skins.CurrentSkinInfo.Value = DefaultSkin.CreateInfo().ToLiveUnmanaged()); } [Test] public void TestLegacySkin() { - AddStep("set legacy skin", () => skins.CurrentSkinInfo.Value = DefaultLegacySkin.CreateInfo().ToLive()); + AddStep("set legacy skin", () => skins.CurrentSkinInfo.Value = DefaultLegacySkin.CreateInfo().ToLiveUnmanaged()); } } } diff --git a/osu.Game.Tests/Database/RealmLiveTests.cs b/osu.Game.Tests/Database/RealmLiveTests.cs index 1d197791a4..06cb5a3607 100644 --- a/osu.Game.Tests/Database/RealmLiveTests.cs +++ b/osu.Game.Tests/Database/RealmLiveTests.cs @@ -22,9 +22,9 @@ namespace osu.Game.Tests.Database { RunTestWithRealm((realmFactory, _) => { - ILive beatmap = realmFactory.CreateContext().Write(r => r.Add(new RealmBeatmap(CreateRuleset(), new RealmBeatmapDifficulty(), new RealmBeatmapMetadata()))).ToLive(); + ILive beatmap = realmFactory.CreateContext().Write(r => r.Add(new RealmBeatmap(CreateRuleset(), new RealmBeatmapDifficulty(), new RealmBeatmapMetadata()))).ToLive(realmFactory); - ILive beatmap2 = realmFactory.CreateContext().All().First().ToLive(); + ILive beatmap2 = realmFactory.CreateContext().All().First().ToLive(realmFactory); Assert.AreEqual(beatmap, beatmap2); }); @@ -43,7 +43,7 @@ namespace osu.Game.Tests.Database { context.Write(r => r.Add(beatmap)); - liveBeatmap = beatmap.ToLive(); + liveBeatmap = beatmap.ToLive(realmFactory); } using (var migratedStorage = new TemporaryNativeStorage("realm-test-migration-target")) @@ -64,7 +64,7 @@ namespace osu.Game.Tests.Database { var beatmap = new RealmBeatmap(CreateRuleset(), new RealmBeatmapDifficulty(), new RealmBeatmapMetadata()); - var liveBeatmap = beatmap.ToLive(); + var liveBeatmap = beatmap.ToLive(realmFactory); using (var context = realmFactory.CreateContext()) context.Write(r => r.Add(beatmap)); @@ -77,7 +77,7 @@ namespace osu.Game.Tests.Database public void TestAccessNonManaged() { var beatmap = new RealmBeatmap(CreateRuleset(), new RealmBeatmapDifficulty(), new RealmBeatmapMetadata()); - var liveBeatmap = beatmap.ToLive(); + var liveBeatmap = beatmap.ToLiveUnmanaged(); Assert.IsFalse(beatmap.Hidden); Assert.IsFalse(liveBeatmap.Value.Hidden); @@ -102,7 +102,7 @@ namespace osu.Game.Tests.Database { var beatmap = threadContext.Write(r => r.Add(new RealmBeatmap(CreateRuleset(), new RealmBeatmapDifficulty(), new RealmBeatmapMetadata()))); - liveBeatmap = beatmap.ToLive(); + liveBeatmap = beatmap.ToLive(realmFactory); } }, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).Wait(); @@ -131,7 +131,7 @@ namespace osu.Game.Tests.Database { var beatmap = threadContext.Write(r => r.Add(new RealmBeatmap(CreateRuleset(), new RealmBeatmapDifficulty(), new RealmBeatmapMetadata()))); - liveBeatmap = beatmap.ToLive(); + liveBeatmap = beatmap.ToLive(realmFactory); } }, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).Wait(); @@ -151,7 +151,7 @@ namespace osu.Game.Tests.Database RunTestWithRealm((realmFactory, _) => { var beatmap = new RealmBeatmap(CreateRuleset(), new RealmBeatmapDifficulty(), new RealmBeatmapMetadata()); - var liveBeatmap = beatmap.ToLive(); + var liveBeatmap = beatmap.ToLive(realmFactory); Assert.DoesNotThrow(() => { @@ -173,7 +173,7 @@ namespace osu.Game.Tests.Database { var beatmap = threadContext.Write(r => r.Add(new RealmBeatmap(CreateRuleset(), new RealmBeatmapDifficulty(), new RealmBeatmapMetadata()))); - liveBeatmap = beatmap.ToLive(); + liveBeatmap = beatmap.ToLive(realmFactory); } }, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).Wait(); @@ -211,7 +211,7 @@ namespace osu.Game.Tests.Database { var beatmap = threadContext.Write(r => r.Add(new RealmBeatmap(CreateRuleset(), new RealmBeatmapDifficulty(), new RealmBeatmapMetadata()))); - liveBeatmap = beatmap.ToLive(); + liveBeatmap = beatmap.ToLive(realmFactory); } }, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).Wait(); @@ -250,7 +250,7 @@ namespace osu.Game.Tests.Database // not just a refresh from the resolved Live. threadContext.Write(r => r.Add(new RealmBeatmap(ruleset, new RealmBeatmapDifficulty(), new RealmBeatmapMetadata()))); - liveBeatmap = beatmap.ToLive(); + liveBeatmap = beatmap.ToLive(realmFactory); } }, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).Wait(); diff --git a/osu.Game.Tests/Visual/Background/TestSceneBackgroundScreenDefault.cs b/osu.Game.Tests/Visual/Background/TestSceneBackgroundScreenDefault.cs index bdd1b92c8d..4762d3cded 100644 --- a/osu.Game.Tests/Visual/Background/TestSceneBackgroundScreenDefault.cs +++ b/osu.Game.Tests/Visual/Background/TestSceneBackgroundScreenDefault.cs @@ -123,7 +123,7 @@ namespace osu.Game.Tests.Visual.Background private void setCustomSkin() { // feign a skin switch. this doesn't do anything except force CurrentSkin to become a LegacySkin. - AddStep("set custom skin", () => skins.CurrentSkinInfo.Value = new SkinInfo().ToLive()); + AddStep("set custom skin", () => skins.CurrentSkinInfo.Value = new SkinInfo().ToLiveUnmanaged()); } private void setDefaultSkin() => AddStep("set default skin", () => skins.CurrentSkinInfo.SetDefault()); diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs index cccc962a3f..c5f56cae9e 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs @@ -53,7 +53,7 @@ namespace osu.Game.Tests.Visual.Gameplay { AddStep("setup skins", () => { - skinManager.CurrentSkinInfo.Value = gameCurrentSkin.ToLive(); + skinManager.CurrentSkinInfo.Value = gameCurrentSkin.ToLiveUnmanaged(); currentBeatmapSkin = getBeatmapSkin(); }); }); diff --git a/osu.Game/Database/RealmLive.cs b/osu.Game/Database/RealmLive.cs index c376d5d503..4f7bdf93e4 100644 --- a/osu.Game/Database/RealmLive.cs +++ b/osu.Game/Database/RealmLive.cs @@ -24,14 +24,22 @@ namespace osu.Game.Database /// private readonly T data; + private readonly RealmContextFactory? realmFactory; + /// /// Construct a new instance of live realm data. /// /// The realm data. - public RealmLive(T data) + /// The realm factory the data was sourced from. May be null for an unmanaged object. + public RealmLive(T data, RealmContextFactory? realmFactory) { this.data = data; + if (IsManaged && realmFactory == null) + throw new ArgumentException(@"Realm factory must be provided for a managed instance", nameof(realmFactory)); + + this.realmFactory = realmFactory; + ID = data.ID; } @@ -47,7 +55,10 @@ namespace osu.Game.Database return; } - using (var realm = Realm.GetInstance(data.Realm.Config)) + if (realmFactory == null) + throw new ArgumentException(@"Realm factory must be provided for a managed instance", nameof(realmFactory)); + + using (var realm = realmFactory.CreateContext()) perform(realm.Find(ID)); } @@ -58,12 +69,15 @@ namespace osu.Game.Database public TReturn PerformRead(Func perform) { if (typeof(RealmObjectBase).IsAssignableFrom(typeof(TReturn))) - throw new InvalidOperationException($"Realm live objects should not exit the scope of {nameof(PerformRead)}."); + throw new InvalidOperationException(@$"Realm live objects should not exit the scope of {nameof(PerformRead)}."); if (!IsManaged) return perform(data); - using (var realm = Realm.GetInstance(data.Realm.Config)) + if (realmFactory == null) + throw new ArgumentException(@"Realm factory must be provided for a managed instance", nameof(realmFactory)); + + using (var realm = realmFactory.CreateContext()) return perform(realm.Find(ID)); } @@ -74,7 +88,7 @@ namespace osu.Game.Database public void PerformWrite(Action perform) { if (!IsManaged) - throw new InvalidOperationException("Can't perform writes on a non-managed underlying value"); + throw new InvalidOperationException(@"Can't perform writes on a non-managed underlying value"); PerformRead(t => { @@ -94,11 +108,7 @@ namespace osu.Game.Database if (!ThreadSafety.IsUpdateThread) throw new InvalidOperationException($"Can't use {nameof(Value)} on managed objects from non-update threads"); - // When using Value, we rely on garbage collection for the realm instance used to retrieve the instance. - // As we are sure that this is on the update thread, there should always be an open and constantly refreshing realm instance to ensure file size growth is a non-issue. - var realm = Realm.GetInstance(data.Realm.Config); - - return realm.Find(ID); + return realmFactory!.Context.Find(ID); } } diff --git a/osu.Game/Database/RealmObjectExtensions.cs b/osu.Game/Database/RealmObjectExtensions.cs index b38e21453c..c546a70fae 100644 --- a/osu.Game/Database/RealmObjectExtensions.cs +++ b/osu.Game/Database/RealmObjectExtensions.cs @@ -53,16 +53,28 @@ namespace osu.Game.Database return mapper.Map(item); } - public static List> ToLive(this IEnumerable realmList) + public static List> ToLiveUnmanaged(this IEnumerable realmList) where T : RealmObject, IHasGuidPrimaryKey { - return realmList.Select(l => new RealmLive(l)).Cast>().ToList(); + return realmList.Select(l => new RealmLive(l, null)).Cast>().ToList(); } - public static ILive ToLive(this T realmObject) + public static ILive ToLiveUnmanaged(this T realmObject) where T : RealmObject, IHasGuidPrimaryKey { - return new RealmLive(realmObject); + return new RealmLive(realmObject, null); + } + + public static List> ToLive(this IEnumerable realmList, RealmContextFactory realmContextFactory) + where T : RealmObject, IHasGuidPrimaryKey + { + return realmList.Select(l => new RealmLive(l, realmContextFactory)).Cast>().ToList(); + } + + public static ILive ToLive(this T realmObject, RealmContextFactory realmContextFactory) + where T : RealmObject, IHasGuidPrimaryKey + { + return new RealmLive(realmObject, realmContextFactory); } /// diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index a35191613c..9c379de683 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -255,10 +255,10 @@ namespace osu.Game if (skinInfo == null) { if (guid == SkinInfo.CLASSIC_SKIN) - skinInfo = DefaultLegacySkin.CreateInfo().ToLive(); + skinInfo = DefaultLegacySkin.CreateInfo().ToLiveUnmanaged(); } - SkinManager.CurrentSkinInfo.Value = skinInfo ?? DefaultSkin.CreateInfo().ToLive(); + SkinManager.CurrentSkinInfo.Value = skinInfo ?? DefaultSkin.CreateInfo().ToLiveUnmanaged(); }; configSkin.TriggerChange(); diff --git a/osu.Game/Overlays/Settings/Sections/SkinSection.cs b/osu.Game/Overlays/Settings/Sections/SkinSection.cs index b1582d7bee..0fa6d78d4b 100644 --- a/osu.Game/Overlays/Settings/Sections/SkinSection.cs +++ b/osu.Game/Overlays/Settings/Sections/SkinSection.cs @@ -32,14 +32,14 @@ namespace osu.Game.Overlays.Settings.Sections Icon = FontAwesome.Solid.PaintBrush }; - private readonly Bindable> dropdownBindable = new Bindable> { Default = DefaultSkin.CreateInfo().ToLive() }; + private readonly Bindable> dropdownBindable = new Bindable> { Default = DefaultSkin.CreateInfo().ToLiveUnmanaged() }; private readonly Bindable configBindable = new Bindable(); private static readonly ILive random_skin_info = new SkinInfo { ID = SkinInfo.RANDOM_SKIN, Name = "", - }.ToLive(); + }.ToLiveUnmanaged(); private List> skinItems; @@ -133,7 +133,7 @@ namespace osu.Game.Overlays.Settings.Sections { int protectedCount = realmSkins.Count(s => s.Protected); - skinItems = realmSkins.ToLive(); + skinItems = realmSkins.ToLive(realmFactory); skinItems.Insert(protectedCount, random_skin_info); diff --git a/osu.Game/Skinning/Skin.cs b/osu.Game/Skinning/Skin.cs index 54fc2340f1..ee92b6b40a 100644 --- a/osu.Game/Skinning/Skin.cs +++ b/osu.Game/Skinning/Skin.cs @@ -43,7 +43,7 @@ namespace osu.Game.Skinning protected Skin(SkinInfo skin, IStorageResourceProvider resources, [CanBeNull] Stream configurationStream = null) { - SkinInfo = skin.ToLive(); + SkinInfo = skin.ToLive(resources.RealmContextFactory); this.resources = resources; configurationStream ??= getConfigurationStream(); diff --git a/osu.Game/Skinning/SkinManager.cs b/osu.Game/Skinning/SkinManager.cs index 5134632fb1..bb2f0a37b4 100644 --- a/osu.Game/Skinning/SkinManager.cs +++ b/osu.Game/Skinning/SkinManager.cs @@ -47,9 +47,9 @@ namespace osu.Game.Skinning public readonly Bindable CurrentSkin = new Bindable(); - public readonly Bindable> CurrentSkinInfo = new Bindable>(Skinning.DefaultSkin.CreateInfo().ToLive()) + public readonly Bindable> CurrentSkinInfo = new Bindable>(Skinning.DefaultSkin.CreateInfo().ToLiveUnmanaged()) { - Default = Skinning.DefaultSkin.CreateInfo().ToLive() + Default = Skinning.DefaultSkin.CreateInfo().ToLiveUnmanaged() }; private readonly SkinModelManager skinModelManager; @@ -119,13 +119,13 @@ namespace osu.Game.Skinning if (randomChoices.Length == 0) { - CurrentSkinInfo.Value = Skinning.DefaultSkin.CreateInfo().ToLive(); + CurrentSkinInfo.Value = Skinning.DefaultSkin.CreateInfo().ToLiveUnmanaged(); return; } var chosen = randomChoices.ElementAt(RNG.Next(0, randomChoices.Length)); - CurrentSkinInfo.Value = chosen.ToLive(); + CurrentSkinInfo.Value = chosen.ToLive(contextFactory); } } @@ -182,7 +182,7 @@ namespace osu.Game.Skinning public ILive Query(Expression> query) { using (var context = contextFactory.CreateContext()) - return context.All().FirstOrDefault(query)?.ToLive(); + return context.All().FirstOrDefault(query)?.ToLive(contextFactory); } public event Action SourceChanged; @@ -237,6 +237,7 @@ namespace osu.Game.Skinning AudioManager IStorageResourceProvider.AudioManager => audio; IResourceStore IStorageResourceProvider.Resources => resources; IResourceStore IStorageResourceProvider.Files => userFiles; + RealmContextFactory IStorageResourceProvider.RealmContextFactory => contextFactory; IResourceStore IStorageResourceProvider.CreateTextureLoaderStore(IResourceStore underlyingStore) => host.CreateTextureLoaderStore(underlyingStore); #endregion @@ -302,7 +303,7 @@ namespace osu.Game.Skinning Guid currentUserSkin = CurrentSkinInfo.Value.ID; if (items.Any(s => s.ID == currentUserSkin)) - scheduler.Add(() => CurrentSkinInfo.Value = Skinning.DefaultSkin.CreateInfo().ToLive()); + scheduler.Add(() => CurrentSkinInfo.Value = Skinning.DefaultSkin.CreateInfo().ToLiveUnmanaged()); skinModelManager.Delete(items.ToList(), silent); } diff --git a/osu.Game/Stores/RealmArchiveModelImporter.cs b/osu.Game/Stores/RealmArchiveModelImporter.cs index 1681dad750..4aca079e2e 100644 --- a/osu.Game/Stores/RealmArchiveModelImporter.cs +++ b/osu.Game/Stores/RealmArchiveModelImporter.cs @@ -352,7 +352,7 @@ namespace osu.Game.Stores transaction.Commit(); } - return existing.ToLive(); + return existing.ToLive(ContextFactory); } LogForModel(item, @"Found existing (optimised) but failed pre-check."); @@ -387,7 +387,7 @@ namespace osu.Game.Stores existing.DeletePending = false; transaction.Commit(); - return existing.ToLive(); + return existing.ToLive(ContextFactory); } LogForModel(item, @"Found existing but failed re-use check."); @@ -416,7 +416,7 @@ namespace osu.Game.Stores throw; } - return item.ToLive(); + return item.ToLive(ContextFactory); } } From eb3050b2ac9fd4245d9006eb300e844ed90c3178 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 14 Dec 2021 15:08:00 +0900 Subject: [PATCH 105/146] Fix incorrect test --- osu.Game.Tests/Visual/Gameplay/TestSceneLeadIn.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneLeadIn.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneLeadIn.cs index f5f17a0bc1..e03c8d7561 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneLeadIn.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneLeadIn.cs @@ -85,11 +85,12 @@ namespace osu.Game.Tests.Visual.Gameplay loopGroup.Scale.Add(Easing.None, -20000, -18000, 0, 1); var target = addEventToLoop ? loopGroup : sprite.TimelineGroup; - target.Alpha.Add(Easing.None, firstStoryboardEvent, firstStoryboardEvent + 500, 0, 1); + double targetTime = addEventToLoop ? 20000 : 0; + target.Alpha.Add(Easing.None, targetTime + firstStoryboardEvent, targetTime + firstStoryboardEvent + 500, 0, 1); // these should be ignored due to being in the future. sprite.TimelineGroup.Alpha.Add(Easing.None, 18000, 20000, 0, 1); - loopGroup.Alpha.Add(Easing.None, 18000, 20000, 0, 1); + loopGroup.Alpha.Add(Easing.None, 38000, 40000, 0, 1); storyboard.GetLayer("Background").Add(sprite); From b6a272e31a0fe8300761532c5688b3b0320d6e4a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 14 Dec 2021 16:10:53 +0900 Subject: [PATCH 106/146] Add failing test coverage of `BackgroundScreeNDefault`'s beatmap background tracking when active/non-active --- .../TestSceneBackgroundScreenDefault.cs | 134 +++++++++++++++++- .../Backgrounds/BackgroundScreenDefault.cs | 2 +- 2 files changed, 129 insertions(+), 7 deletions(-) diff --git a/osu.Game.Tests/Visual/Background/TestSceneBackgroundScreenDefault.cs b/osu.Game.Tests/Visual/Background/TestSceneBackgroundScreenDefault.cs index bdd1b92c8d..476eadf9bb 100644 --- a/osu.Game.Tests/Visual/Background/TestSceneBackgroundScreenDefault.cs +++ b/osu.Game.Tests/Visual/Background/TestSceneBackgroundScreenDefault.cs @@ -5,8 +5,11 @@ using System; using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; +using osu.Framework.Audio; +using osu.Framework.Graphics.Textures; using osu.Framework.Screens; using osu.Framework.Testing; +using osu.Game.Beatmaps; using osu.Game.Configuration; using osu.Game.Database; using osu.Game.Graphics.Backgrounds; @@ -15,6 +18,7 @@ using osu.Game.Online.API.Requests.Responses; using osu.Game.Screens; using osu.Game.Screens.Backgrounds; using osu.Game.Skinning; +using osu.Game.Tests.Beatmaps; namespace osu.Game.Tests.Visual.Background { @@ -22,8 +26,7 @@ namespace osu.Game.Tests.Visual.Background public class TestSceneBackgroundScreenDefault : OsuTestScene { private BackgroundScreenStack stack; - private BackgroundScreenDefault screen; - + private TestBackgroundScreenDefault screen; private Graphics.Backgrounds.Background getCurrentBackground() => screen.ChildrenOfType().FirstOrDefault(); [Resolved] @@ -36,10 +39,95 @@ namespace osu.Game.Tests.Visual.Background public void SetUpSteps() { AddStep("create background stack", () => Child = stack = new BackgroundScreenStack()); - AddStep("push default screen", () => stack.Push(screen = new BackgroundScreenDefault(false))); + AddStep("push default screen", () => stack.Push(screen = new TestBackgroundScreenDefault())); AddUntilStep("wait for screen to load", () => screen.IsCurrentScreen()); } + [Test] + public void TestBeatmapBackgroundTracksBeatmap() + { + setSupporter(true); + setSourceMode(BackgroundSource.Beatmap); + + AddStep("change beatmap", () => Beatmap.Value = createTestWorkingBeatmapWithUniqueBackground()); + AddAssert("background changed", () => screen.CheckLastLoadChange() == true); + + Graphics.Backgrounds.Background last = null; + + AddUntilStep("wait for beatmap background to be loaded", () => getCurrentBackground()?.GetType() == typeof(BeatmapBackground)); + AddStep("store background", () => last = getCurrentBackground()); + + AddStep("change beatmap", () => Beatmap.Value = createTestWorkingBeatmapWithUniqueBackground()); + + AddUntilStep("wait for beatmap background to change", () => screen.CheckLastLoadChange() == true); + + AddUntilStep("background is new beatmap background", () => last != getCurrentBackground()); + AddStep("store background", () => last = getCurrentBackground()); + + AddStep("change beatmap", () => Beatmap.Value = createTestWorkingBeatmapWithUniqueBackground()); + + AddUntilStep("wait for beatmap background to change", () => screen.CheckLastLoadChange() == true); + AddUntilStep("background is new beatmap background", () => last != getCurrentBackground()); + } + + [Test] + public void TestBeatmapBackgroundTracksBeatmapWhenSuspended() + { + setSupporter(true); + setSourceMode(BackgroundSource.Beatmap); + + AddStep("change beatmap", () => Beatmap.Value = createTestWorkingBeatmapWithUniqueBackground()); + AddAssert("background changed", () => screen.CheckLastLoadChange() == true); + AddUntilStep("wait for beatmap background to be loaded", () => getCurrentBackground()?.GetType() == typeof(BeatmapBackground)); + + BackgroundScreenBeatmap nestedScreen = null; + + // of note, this needs to be a type that doesn't match BackgroundScreenDefault else it is silently not pushed by the background stack. + AddStep("push new background to stack", () => stack.Push(nestedScreen = new BackgroundScreenBeatmap(Beatmap.Value))); + AddUntilStep("wait for screen to load", () => nestedScreen.IsLoaded && nestedScreen.IsCurrentScreen()); + + AddAssert("top level background hasn't changed yet", () => screen.CheckLastLoadChange() == null); + + AddStep("change beatmap", () => Beatmap.Value = createTestWorkingBeatmapWithUniqueBackground()); + + AddAssert("top level background hasn't changed yet", () => screen.CheckLastLoadChange() == null); + + AddStep("pop screen back to top level", () => screen.MakeCurrent()); + + AddAssert("top level background changed", () => screen.CheckLastLoadChange() == true); + } + + [Test] + public void TestBeatmapBackgroundIgnoresNoChangeWhenSuspended() + { + BackgroundScreenBeatmap nestedScreen = null; + WorkingBeatmap originalWorking = null; + + setSupporter(true); + setSourceMode(BackgroundSource.Beatmap); + + AddStep("change beatmap", () => originalWorking = Beatmap.Value = createTestWorkingBeatmapWithUniqueBackground()); + AddAssert("background changed", () => screen.CheckLastLoadChange() == true); + AddUntilStep("wait for beatmap background to be loaded", () => getCurrentBackground()?.GetType() == typeof(BeatmapBackground)); + + // of note, this needs to be a type that doesn't match BackgroundScreenDefault else it is silently not pushed by the background stack. + AddStep("push new background to stack", () => stack.Push(nestedScreen = new BackgroundScreenBeatmap(Beatmap.Value))); + AddUntilStep("wait for screen to load", () => nestedScreen.IsLoaded && nestedScreen.IsCurrentScreen()); + + // we're testing a case where scheduling may be used to avoid issues, so ensure the scheduler is no longer running. + AddUntilStep("wait for top level not alive", () => !screen.IsAlive); + + AddStep("change beatmap", () => Beatmap.Value = createTestWorkingBeatmapWithUniqueBackground()); + AddStep("change beatmap back", () => Beatmap.Value = originalWorking); + + AddAssert("top level background hasn't changed yet", () => screen.CheckLastLoadChange() == null); + + AddStep("pop screen back to top level", () => screen.MakeCurrent()); + + AddStep("top level screen is current", () => screen.IsCurrentScreen()); + AddAssert("top level background reused existing", () => screen.CheckLastLoadChange() == false); + } + [Test] public void TestBackgroundTypeSwitch() { @@ -96,13 +184,11 @@ namespace osu.Game.Tests.Visual.Background [Test] public void TestBackgroundCyclingOnDefaultSkin([Values] bool supporter) { - Graphics.Backgrounds.Background last = null; - setSourceMode(BackgroundSource.Skin); setSupporter(supporter); setDefaultSkin(); - AddUntilStep("wait for beatmap background to be loaded", () => (last = getCurrentBackground())?.GetType() == typeof(Graphics.Backgrounds.Background)); + AddUntilStep("wait for beatmap background to be loaded", () => (getCurrentBackground())?.GetType() == typeof(Graphics.Backgrounds.Background)); AddAssert("next cycles background", () => screen.Next()); // doesn't really need to be checked but might as well. @@ -120,6 +206,42 @@ namespace osu.Game.Tests.Visual.Background Id = API.LocalUser.Value.Id + 1, }); + private WorkingBeatmap createTestWorkingBeatmapWithUniqueBackground() => new UniqueBackgroundTestWorkingBeatmap(Audio); + + private class TestBackgroundScreenDefault : BackgroundScreenDefault + { + private bool? lastLoadTriggerCausedChange; + + public TestBackgroundScreenDefault() + : base(false) + { + } + + public override bool Next() + { + bool didChange = base.Next(); + lastLoadTriggerCausedChange = didChange; + return didChange; + } + + public bool? CheckLastLoadChange() + { + bool? lastChange = lastLoadTriggerCausedChange; + lastLoadTriggerCausedChange = null; + return lastChange; + } + } + + private class UniqueBackgroundTestWorkingBeatmap : TestWorkingBeatmap + { + public UniqueBackgroundTestWorkingBeatmap(AudioManager audioManager) + : base(new Beatmap(), null, audioManager) + { + } + + protected override Texture GetBackground() => new Texture(1, 1); + } + private void setCustomSkin() { // feign a skin switch. this doesn't do anything except force CurrentSkin to become a LegacySkin. diff --git a/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs b/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs index 4a922c45b9..a1b1b52c14 100644 --- a/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs +++ b/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs @@ -67,7 +67,7 @@ namespace osu.Game.Screens.Backgrounds /// Request loading the next background. /// /// Whether a new background was queued for load. May return false if the current background is still valid. - public bool Next() + public virtual bool Next() { var nextBackground = createBackground(); From 25a056dfad45269be89e509a6554ad3c61ca02f1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 14 Dec 2021 16:35:18 +0900 Subject: [PATCH 107/146] Remove pointless/broken test steps These aren't accurate and are tested via a more accurate means directly above. --- .../Background/TestSceneBackgroundScreenDefault.cs | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/osu.Game.Tests/Visual/Background/TestSceneBackgroundScreenDefault.cs b/osu.Game.Tests/Visual/Background/TestSceneBackgroundScreenDefault.cs index 476eadf9bb..1d3dd35e1d 100644 --- a/osu.Game.Tests/Visual/Background/TestSceneBackgroundScreenDefault.cs +++ b/osu.Game.Tests/Visual/Background/TestSceneBackgroundScreenDefault.cs @@ -166,19 +166,13 @@ namespace osu.Game.Tests.Visual.Background [TestCase(BackgroundSource.Skin, typeof(SkinBackground))] public void TestBackgroundDoesntReloadOnNoChange(BackgroundSource source, Type backgroundType) { - Graphics.Backgrounds.Background last = null; - setSourceMode(source); setSupporter(true); if (source == BackgroundSource.Skin) setCustomSkin(); - AddUntilStep("wait for beatmap background to be loaded", () => (last = getCurrentBackground())?.GetType() == backgroundType); + AddUntilStep("wait for beatmap background to be loaded", () => (getCurrentBackground())?.GetType() == backgroundType); AddAssert("next doesn't load new background", () => screen.Next() == false); - - // doesn't really need to be checked but might as well. - AddWaitStep("wait a bit", 5); - AddUntilStep("ensure same background instance", () => last == getCurrentBackground()); } [Test] @@ -190,10 +184,6 @@ namespace osu.Game.Tests.Visual.Background AddUntilStep("wait for beatmap background to be loaded", () => (getCurrentBackground())?.GetType() == typeof(Graphics.Backgrounds.Background)); AddAssert("next cycles background", () => screen.Next()); - - // doesn't really need to be checked but might as well. - AddWaitStep("wait a bit", 5); - AddUntilStep("ensure different background instance", () => last != getCurrentBackground()); } private void setSourceMode(BackgroundSource source) => From 8c6f50ddb1b3f7993485a06129803e442b6061fd Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 14 Dec 2021 15:17:01 +0900 Subject: [PATCH 108/146] Fix `BackgroundScreenDefault` incorrectly updating current background after being inactive If the beatmap was changed but then reverted to the previously displayed map, the background may have displayed incorrectly on resuming. Closes #15804. --- .../Backgrounds/BackgroundScreenDefault.cs | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs b/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs index a1b1b52c14..452f033dcc 100644 --- a/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs +++ b/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs @@ -48,16 +48,19 @@ namespace osu.Game.Screens.Backgrounds AddInternal(seasonalBackgroundLoader); - user.ValueChanged += _ => Next(); - skin.ValueChanged += _ => Next(); - mode.ValueChanged += _ => Next(); - beatmap.ValueChanged += _ => Next(); - introSequence.ValueChanged += _ => Next(); - seasonalBackgroundLoader.SeasonalBackgroundChanged += () => Next(); + user.ValueChanged += _ => Scheduler.AddOnce(loadNextIfRequired); + skin.ValueChanged += _ => Scheduler.AddOnce(loadNextIfRequired); + mode.ValueChanged += _ => Scheduler.AddOnce(loadNextIfRequired); + beatmap.ValueChanged += _ => Scheduler.AddOnce(loadNextIfRequired); + introSequence.ValueChanged += _ => Scheduler.AddOnce(loadNextIfRequired); + seasonalBackgroundLoader.SeasonalBackgroundChanged += () => Scheduler.AddOnce(loadNextIfRequired); currentDisplay = RNG.Next(0, background_count); Next(); + + // helper function required for AddOnce usage. + void loadNextIfRequired() => Next(); } private ScheduledDelegate nextTask; From 79dd9674fc5a580a10d3005d272a1b94b4aaff4c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 14 Dec 2021 17:41:02 +0900 Subject: [PATCH 109/146] Use longer form to read better Using `l.StartTime` reads like a coding issue, even though if you go down the call chain looks to be correct. --- osu.Game/Storyboards/StoryboardSprite.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Storyboards/StoryboardSprite.cs b/osu.Game/Storyboards/StoryboardSprite.cs index f941cec20c..ebd1a941a8 100644 --- a/osu.Game/Storyboards/StoryboardSprite.cs +++ b/osu.Game/Storyboards/StoryboardSprite.cs @@ -33,8 +33,8 @@ namespace osu.Game.Storyboards foreach (var l in loops) { - if (l.EarliestDisplayedTime != null) - earliestStartTime = Math.Min(earliestStartTime, l.StartTime); + if (l.EarliestDisplayedTime is double loopEarliestDisplayTime) + earliestStartTime = Math.Min(earliestStartTime, l.LoopStartTime + loopEarliestDisplayTime); } if (earliestStartTime < double.MaxValue) From 3bc2de4889cb0874c9a3d4adb0392ab06523f10a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 14 Dec 2021 19:11:23 +0900 Subject: [PATCH 110/146] Add failing test coverage of modified beatmap import breaking online availability state --- .../TestSceneOnlinePlayBeatmapAvailabilityTracker.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs b/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs index 24824b1e23..239c787349 100644 --- a/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs +++ b/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs @@ -114,18 +114,23 @@ namespace osu.Game.Tests.Online public void TestTrackerRespectsChecksum() { AddStep("allow importing", () => beatmaps.AllowImport.SetResult(true)); + AddStep("import beatmap", () => beatmaps.Import(testBeatmapFile).Wait()); + addAvailabilityCheckStep("initially locally available", BeatmapAvailability.LocallyAvailable); AddStep("import altered beatmap", () => { beatmaps.Import(TestResources.GetTestBeatmapForImport(true)).Wait(); }); - addAvailabilityCheckStep("state still not downloaded", BeatmapAvailability.NotDownloaded); + addAvailabilityCheckStep("state not downloaded", BeatmapAvailability.NotDownloaded); AddStep("recreate tracker", () => Child = availabilityTracker = new OnlinePlayBeatmapAvailabilityTracker { SelectedItem = { BindTarget = selectedItem } }); addAvailabilityCheckStep("state not downloaded as well", BeatmapAvailability.NotDownloaded); + + AddStep("reimport original beatmap", () => beatmaps.Import(TestResources.GetQuickTestBeatmapForImport()).Wait()); + addAvailabilityCheckStep("locally available after re-import", BeatmapAvailability.LocallyAvailable); } private void addAvailabilityCheckStep(string description, Func expected) From 453ecd21b32b552947ee1141f793e26d88774113 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 14 Dec 2021 19:11:56 +0900 Subject: [PATCH 111/146] Fix `OnlinePlayBeatmapAvailabilityTracker` potentially in incorrect state Adter an import of a modified version of a beatmap (that was already present in the local database), it's feasible that one of these trackers would not see the state change due to the nuances of the import process. --- .../OnlinePlayBeatmapAvailabilityTracker.cs | 45 +++++++++++++++++-- 1 file changed, 42 insertions(+), 3 deletions(-) diff --git a/osu.Game/Online/Rooms/OnlinePlayBeatmapAvailabilityTracker.cs b/osu.Game/Online/Rooms/OnlinePlayBeatmapAvailabilityTracker.cs index aa0e37363b..a32f069470 100644 --- a/osu.Game/Online/Rooms/OnlinePlayBeatmapAvailabilityTracker.cs +++ b/osu.Game/Online/Rooms/OnlinePlayBeatmapAvailabilityTracker.cs @@ -40,6 +40,11 @@ namespace osu.Game.Online.Rooms private BeatmapDownloadTracker downloadTracker; + /// + /// The beatmap matching the required hash (and providing a final state). + /// + private BeatmapInfo matchingHash; + protected override void LoadComplete() { base.LoadComplete(); @@ -71,13 +76,34 @@ namespace osu.Game.Online.Rooms progressUpdate = Scheduler.AddDelayed(updateAvailability, progressUpdate == null ? 0 : 500); }, true); }, true); + + // These events are needed for a fringe case where a modified/altered beatmap is imported with matching OnlineIDs. + // During the import process this will cause the existing beatmap set to be silently deleted and replaced with the new one. + // This is not exposed to us via `BeatmapDownloadTracker` so we have to take it into our own hands (as we care about the hash matching). + beatmapManager.ItemUpdated += itemUpdated; + beatmapManager.ItemRemoved += itemRemoved; } + private void itemUpdated(BeatmapSetInfo item) => Schedule(() => + { + if (matchingHash?.BeatmapSet.ID == item.ID || SelectedItem.Value?.Beatmap.Value.BeatmapSet?.OnlineID == item.OnlineID) + updateAvailability(); + }); + + private void itemRemoved(BeatmapSetInfo item) => Schedule(() => + { + if (matchingHash?.BeatmapSet.ID == item.ID) + updateAvailability(); + }); + private void updateAvailability() { if (downloadTracker == null) return; + // will be repopulated below if still valid. + matchingHash = null; + switch (downloadTracker.State.Value) { case DownloadState.NotDownloaded: @@ -93,7 +119,9 @@ namespace osu.Game.Online.Rooms break; case DownloadState.LocallyAvailable: - bool hashMatches = checkHashValidity(); + matchingHash = findMatchingHash(); + + bool hashMatches = matchingHash != null; availability.Value = hashMatches ? BeatmapAvailability.LocallyAvailable() : BeatmapAvailability.NotDownloaded(); @@ -108,12 +136,23 @@ namespace osu.Game.Online.Rooms } } - private bool checkHashValidity() + private BeatmapInfo findMatchingHash() { int onlineId = SelectedItem.Value.Beatmap.Value.OnlineID; string checksum = SelectedItem.Value.Beatmap.Value.MD5Hash; - return beatmapManager.QueryBeatmap(b => b.OnlineID == onlineId && b.MD5Hash == checksum && !b.BeatmapSet.DeletePending) != null; + return beatmapManager.QueryBeatmap(b => b.OnlineID == onlineId && b.MD5Hash == checksum && !b.BeatmapSet.DeletePending); + } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + + if (beatmapManager != null) + { + beatmapManager.ItemUpdated -= itemUpdated; + beatmapManager.ItemRemoved -= itemRemoved; + } } } } From 0950d8d327eb9d8a85564c917826461dd83b4c6f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 14 Dec 2021 19:16:51 +0900 Subject: [PATCH 112/146] Add back `PlaylistRoomCreation` test Was spiritually removed in https://github.com/ppy/osu/pull/16045. This implementation is mostly taken from that PR's comment thread verbatim, and now works due to the associated changes to `OnlinePlayBeatmapAvailabilityTracker`. --- .../TestScenePlaylistsRoomCreation.cs | 82 +++++++++++++++++++ 1 file changed, 82 insertions(+) diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomCreation.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomCreation.cs index 450e821ba6..9dd8b41dcd 100644 --- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomCreation.cs +++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomCreation.cs @@ -15,9 +15,11 @@ using osu.Game.Database; using osu.Game.Online.Rooms; using osu.Game.Rulesets; using osu.Game.Rulesets.Osu; +using osu.Game.Rulesets.Osu.Objects; using osu.Game.Screens.OnlinePlay.Components; using osu.Game.Screens.OnlinePlay.Playlists; using osu.Game.Screens.Play; +using osu.Game.Tests.Beatmaps; using osu.Game.Tests.Visual.OnlinePlay; using osuTK.Input; @@ -109,7 +111,87 @@ namespace osu.Game.Tests.Visual.Playlists AddAssert("first playlist item selected", () => match.SelectedItem.Value == SelectedRoom.Value.Playlist[0]); } + [Test] + public void TestBeatmapUpdatedOnReImport() + { + string realHash = null; + int realOnlineId = 0; + int realOnlineSetId = 0; + + AddStep("store real beatmap values", () => + { + realHash = importedBeatmap.Value.Beatmaps[0].MD5Hash; + realOnlineId = importedBeatmap.Value.Beatmaps[0].OnlineID ?? -1; + realOnlineSetId = importedBeatmap.Value.OnlineID ?? -1; + }); + + AddStep("import modified beatmap", () => + { + var modifiedBeatmap = new TestBeatmap(new OsuRuleset().RulesetInfo) + { + BeatmapInfo = + { + OnlineID = realOnlineId, + BeatmapSet = + { + OnlineID = realOnlineSetId + } + }, + }; + + modifiedBeatmap.HitObjects.Clear(); + modifiedBeatmap.HitObjects.Add(new HitCircle { StartTime = 5000 }); + + manager.Import(modifiedBeatmap.BeatmapInfo.BeatmapSet).Wait(); + }); + + // Create the room using the real beatmap values. + setupAndCreateRoom(room => + { + room.Name.Value = "my awesome room"; + room.Host.Value = API.LocalUser.Value; + room.Playlist.Add(new PlaylistItem + { + Beatmap = + { + Value = new BeatmapInfo + { + MD5Hash = realHash, + OnlineID = realOnlineId, + BeatmapSet = new BeatmapSetInfo + { + OnlineID = realOnlineSetId, + } + } + }, + Ruleset = { Value = new OsuRuleset().RulesetInfo } + }); + }); + + AddAssert("match has default beatmap", () => match.Beatmap.IsDefault); + + AddStep("reimport original beatmap", () => + { + var originalBeatmap = new TestBeatmap(new OsuRuleset().RulesetInfo) + { + BeatmapInfo = + { + OnlineID = realOnlineId, + BeatmapSet = + { + OnlineID = realOnlineSetId + } + }, + }; + + manager.Import(originalBeatmap.BeatmapInfo.BeatmapSet).Wait(); + }); + + AddUntilStep("match has correct beatmap", () => realHash == match.Beatmap.Value.BeatmapInfo.MD5Hash); + } + private void setupAndCreateRoom(Action room) + { AddStep("setup room", () => room(SelectedRoom.Value)); From 8e79fac389d5f5ad5b90a15c2da7d19dc7219f70 Mon Sep 17 00:00:00 2001 From: tbrose Date: Tue, 14 Dec 2021 16:23:51 +0100 Subject: [PATCH 113/146] Fixes code quality check failed --- .../Online/Chat/MessageNotifierTest.cs | 26 +++++++++---------- osu.Game/Online/Chat/MessageNotifier.cs | 5 ++-- 2 files changed, 15 insertions(+), 16 deletions(-) diff --git a/osu.Game.Tests/Online/Chat/MessageNotifierTest.cs b/osu.Game.Tests/Online/Chat/MessageNotifierTest.cs index b885299d1f..2ec5b778d1 100644 --- a/osu.Game.Tests/Online/Chat/MessageNotifierTest.cs +++ b/osu.Game.Tests/Online/Chat/MessageNotifierTest.cs @@ -12,79 +12,79 @@ namespace osu.Game.Tests.Online.Chat [Test] public void TestContainsUsernameMidlinePositive() { - Assert.IsTrue(MessageNotifier.checkContainsUsername("This is a test message", "Test")); + Assert.IsTrue(MessageNotifier.CheckContainsUsername("This is a test message", "Test")); } [Test] public void TestContainsUsernameStartOfLinePositive() { - Assert.IsTrue(MessageNotifier.checkContainsUsername("Test message", "Test")); + Assert.IsTrue(MessageNotifier.CheckContainsUsername("Test message", "Test")); } [Test] public void TestContainsUsernameEndOfLinePositive() { - Assert.IsTrue(MessageNotifier.checkContainsUsername("This is a test", "Test")); + Assert.IsTrue(MessageNotifier.CheckContainsUsername("This is a test", "Test")); } [Test] public void TestContainsUsernameMidlineNegative() { - Assert.IsFalse(MessageNotifier.checkContainsUsername("This is a testmessage for notifications", "Test")); + Assert.IsFalse(MessageNotifier.CheckContainsUsername("This is a testmessage for notifications", "Test")); } [Test] public void TestContainsUsernameStartOfLineNegative() { - Assert.IsFalse(MessageNotifier.checkContainsUsername("Testmessage", "Test")); + Assert.IsFalse(MessageNotifier.CheckContainsUsername("Testmessage", "Test")); } [Test] public void TestContainsUsernameEndOfLineNegative() { - Assert.IsFalse(MessageNotifier.checkContainsUsername("This is a notificationtest", "Test")); + Assert.IsFalse(MessageNotifier.CheckContainsUsername("This is a notificationtest", "Test")); } [Test] public void TestContainsUsernameBetweenInterpunction() { - Assert.IsTrue(MessageNotifier.checkContainsUsername("Hello 'test'-message", "Test")); + Assert.IsTrue(MessageNotifier.CheckContainsUsername("Hello 'test'-message", "Test")); } [Test] public void TestContainsUsernameUnicode() { - Assert.IsTrue(MessageNotifier.checkContainsUsername("Test \u0460\u0460 message", "\u0460\u0460")); + Assert.IsTrue(MessageNotifier.CheckContainsUsername("Test \u0460\u0460 message", "\u0460\u0460")); } [Test] public void TestContainsUsernameUnicodeNegative() { - Assert.IsFalse(MessageNotifier.checkContainsUsername("Test ha\u0460\u0460o message", "\u0460\u0460")); + Assert.IsFalse(MessageNotifier.CheckContainsUsername("Test ha\u0460\u0460o message", "\u0460\u0460")); } [Test] public void TestContainsUsernameSpecialCharactersPositive() { - Assert.IsTrue(MessageNotifier.checkContainsUsername("Test [#^-^#] message", "[#^-^#]")); + Assert.IsTrue(MessageNotifier.CheckContainsUsername("Test [#^-^#] message", "[#^-^#]")); } [Test] public void TestContainsUsernameSpecialCharactersNegative() { - Assert.IsFalse(MessageNotifier.checkContainsUsername("Test pad[#^-^#]oru message", "[#^-^#]")); + Assert.IsFalse(MessageNotifier.CheckContainsUsername("Test pad[#^-^#]oru message", "[#^-^#]")); } [Test] public void TestContainsUsernameAtSign() { - Assert.IsTrue(MessageNotifier.checkContainsUsername("@username hi", "username")); + Assert.IsTrue(MessageNotifier.CheckContainsUsername("@username hi", "username")); } [Test] public void TestContainsUsernameColon() { - Assert.IsTrue(MessageNotifier.checkContainsUsername("username: hi", "username")); + Assert.IsTrue(MessageNotifier.CheckContainsUsername("username: hi", "username")); } } } diff --git a/osu.Game/Online/Chat/MessageNotifier.cs b/osu.Game/Online/Chat/MessageNotifier.cs index db7c5e47f5..a11af7b305 100644 --- a/osu.Game/Online/Chat/MessageNotifier.cs +++ b/osu.Game/Online/Chat/MessageNotifier.cs @@ -1,7 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; using System.Collections.Generic; using System.Collections.Specialized; using System.Linq; @@ -121,7 +120,7 @@ namespace osu.Game.Online.Chat private void checkForMentions(Channel channel, Message message) { - if (!notifyOnUsername.Value || !checkContainsUsername(message.Content, localUser.Value.Username)) return; + if (!notifyOnUsername.Value || !CheckContainsUsername(message.Content, localUser.Value.Username)) return; notifications.Post(new MentionNotification(message.Sender.Username, channel)); } @@ -130,7 +129,7 @@ namespace osu.Game.Online.Chat /// Checks if mentions . /// This will match against the case where underscores are used instead of spaces (which is how osu-stable handles usernames with spaces). /// - public static bool checkContainsUsername(string message, string username) + public static bool CheckContainsUsername(string message, string username) { string fullName = Regex.Escape(username); string underscoreName = Regex.Escape(username.Replace(' ', '_')); From 4664bb1d29ad5d8d442f97f94e12d5bdc7e57bc0 Mon Sep 17 00:00:00 2001 From: rumoi Date: Wed, 15 Dec 2021 05:16:10 +1300 Subject: [PATCH 114/146] Remove uneeded complexity --- osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs index 30d989cfba..d26808bf04 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs @@ -55,7 +55,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills bool firstDeltaSwitch = false; - int rhythmStart = Math.Min(Previous.Count - 2, 0); + int rhythmStart = 0; while (rhythmStart < Previous.Count - 2 && current.StartTime - Previous[rhythmStart].StartTime < history_time_max) rhythmStart++; From 9ade8069a1b7ded46328c023eeb119e26900d1ce Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 14 Dec 2021 16:52:57 +0900 Subject: [PATCH 115/146] Rename to AbortGameplay() and handle additional states --- osu.Game/Online/Multiplayer/IMultiplayerRoomServer.cs | 2 +- osu.Game/Online/Multiplayer/MultiplayerClient.cs | 2 +- osu.Game/Online/Multiplayer/OnlineMultiplayerClient.cs | 4 ++-- osu.Game/Screens/OnlinePlay/Multiplayer/Multiplayer.cs | 4 +++- osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs | 2 +- 5 files changed, 8 insertions(+), 6 deletions(-) diff --git a/osu.Game/Online/Multiplayer/IMultiplayerRoomServer.cs b/osu.Game/Online/Multiplayer/IMultiplayerRoomServer.cs index 200539def7..073d512f90 100644 --- a/osu.Game/Online/Multiplayer/IMultiplayerRoomServer.cs +++ b/osu.Game/Online/Multiplayer/IMultiplayerRoomServer.cs @@ -80,7 +80,7 @@ namespace osu.Game.Online.Multiplayer /// /// Aborts an ongoing gameplay load. /// - Task AbortLoad(); + Task AbortGameplay(); /// /// Adds an item to the playlist. diff --git a/osu.Game/Online/Multiplayer/MultiplayerClient.cs b/osu.Game/Online/Multiplayer/MultiplayerClient.cs index 78d8362170..ffb9e193d4 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerClient.cs @@ -333,7 +333,7 @@ namespace osu.Game.Online.Multiplayer public abstract Task StartMatch(); - public abstract Task AbortLoad(); + public abstract Task AbortGameplay(); public abstract Task AddPlaylistItem(MultiplayerPlaylistItem item); diff --git a/osu.Game/Online/Multiplayer/OnlineMultiplayerClient.cs b/osu.Game/Online/Multiplayer/OnlineMultiplayerClient.cs index 3062cf8b99..73b87190b1 100644 --- a/osu.Game/Online/Multiplayer/OnlineMultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/OnlineMultiplayerClient.cs @@ -154,12 +154,12 @@ namespace osu.Game.Online.Multiplayer return connection.InvokeAsync(nameof(IMultiplayerServer.StartMatch)); } - public override Task AbortLoad() + public override Task AbortGameplay() { if (!IsConnected.Value) return Task.CompletedTask; - return connection.InvokeAsync(nameof(IMultiplayerServer.AbortLoad)); + return connection.InvokeAsync(nameof(IMultiplayerServer.AbortGameplay)); } public override Task AddPlaylistItem(MultiplayerPlaylistItem item) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Multiplayer.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Multiplayer.cs index c299fd285a..e136627d43 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Multiplayer.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Multiplayer.cs @@ -30,7 +30,9 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer break; case MultiplayerUserState.WaitingForLoad: - client.AbortLoad(); + case MultiplayerUserState.Loaded: + case MultiplayerUserState.Playing: + client.AbortGameplay(); break; default: diff --git a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs index 4c69f8f9d2..767751a808 100644 --- a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs +++ b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs @@ -317,7 +317,7 @@ namespace osu.Game.Tests.Visual.Multiplayer return ((IMultiplayerClient)this).LoadRequested(); } - public override Task AbortLoad() + public override Task AbortGameplay() { Debug.Assert(Room != null); Debug.Assert(LocalUser != null); From da00c020be8cd32aa1ee870d242337fb3cc60a9c Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 15 Dec 2021 07:33:49 +0900 Subject: [PATCH 116/146] Remove whitespace --- .../Visual/Playlists/TestScenePlaylistsRoomCreation.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomCreation.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomCreation.cs index 9dd8b41dcd..a426f075e1 100644 --- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomCreation.cs +++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomCreation.cs @@ -191,7 +191,6 @@ namespace osu.Game.Tests.Visual.Playlists } private void setupAndCreateRoom(Action room) - { AddStep("setup room", () => room(SelectedRoom.Value)); From e662a9f0c44bd5b9dab876f60a90919f5c53e44b Mon Sep 17 00:00:00 2001 From: rumoi Date: Wed, 15 Dec 2021 12:36:45 +1300 Subject: [PATCH 117/146] Remove redundant code. --- .../Difficulty/Skills/Speed.cs | 101 +++++++++--------- 1 file changed, 49 insertions(+), 52 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs index d26808bf04..06d1ef7346 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs @@ -66,67 +66,64 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills OsuDifficultyHitObject prevObj = (OsuDifficultyHitObject)Previous[i]; OsuDifficultyHitObject lastObj = (OsuDifficultyHitObject)Previous[i + 1]; - double currHistoricalDecay = Math.Max(0, (history_time_max - (current.StartTime - currObj.StartTime))) / history_time_max; // scales note 0 to 1 from history to now + double currHistoricalDecay = (history_time_max - (current.StartTime - currObj.StartTime)) / history_time_max; // scales note 0 to 1 from history to now - if (currHistoricalDecay != 0) + currHistoricalDecay = Math.Min((double)(Previous.Count - i) / Previous.Count, currHistoricalDecay); // either we're limited by time or limited by object count. + + double currDelta = currObj.StrainTime; + double prevDelta = prevObj.StrainTime; + double lastDelta = lastObj.StrainTime; + double currRatio = 1.0 + 6.0 * Math.Min(0.5, Math.Pow(Math.Sin(Math.PI / (Math.Min(prevDelta, currDelta) / Math.Max(prevDelta, currDelta))), 2)); // fancy function to calculate rhythmbonuses. + + double windowPenalty = Math.Min(1, Math.Max(0, Math.Abs(prevDelta - currDelta) - greatWindow * 0.6) / (greatWindow * 0.6)); + + windowPenalty = Math.Min(1, windowPenalty); + + double effectiveRatio = windowPenalty * currRatio; + + if (firstDeltaSwitch) { - currHistoricalDecay = Math.Min((double)(Previous.Count - i) / Previous.Count, currHistoricalDecay); // either we're limited by time or limited by object count. - - double currDelta = currObj.StrainTime; - double prevDelta = prevObj.StrainTime; - double lastDelta = lastObj.StrainTime; - double currRatio = 1.0 + 6.0 * Math.Min(0.5, Math.Pow(Math.Sin(Math.PI / (Math.Min(prevDelta, currDelta) / Math.Max(prevDelta, currDelta))), 2)); // fancy function to calculate rhythmbonuses. - - double windowPenalty = Math.Min(1, Math.Max(0, Math.Abs(prevDelta - currDelta) - greatWindow * 0.6) / (greatWindow * 0.6)); - - windowPenalty = Math.Min(1, windowPenalty); - - double effectiveRatio = windowPenalty * currRatio; - - if (firstDeltaSwitch) + if (!(prevDelta > 1.25 * currDelta || prevDelta * 1.25 < currDelta)) { - if (!(prevDelta > 1.25 * currDelta || prevDelta * 1.25 < currDelta)) - { - if (islandSize < 7) - islandSize++; // island is still progressing, count size. - } - else - { - if (Previous[i - 1].BaseObject is Slider) // bpm change is into slider, this is easy acc window - effectiveRatio *= 0.125; - - if (Previous[i].BaseObject is Slider) // bpm change was from a slider, this is easier typically than circle -> circle - effectiveRatio *= 0.25; - - if (previousIslandSize == islandSize) // repeated island size (ex: triplet -> triplet) - effectiveRatio *= 0.25; - - if (previousIslandSize % 2 == islandSize % 2) // repeated island polartiy (2 -> 4, 3 -> 5) - effectiveRatio *= 0.50; - - if (lastDelta > prevDelta + 10 && prevDelta > currDelta + 10) // previous increase happened a note ago, 1/1->1/2-1/4, dont want to buff this. - effectiveRatio *= 0.125; - - rhythmComplexitySum += Math.Sqrt(effectiveRatio * startRatio) * currHistoricalDecay * Math.Sqrt(4 + islandSize) / 2 * Math.Sqrt(4 + previousIslandSize) / 2; - - startRatio = effectiveRatio; - - previousIslandSize = islandSize; // log the last island size. - - if (prevDelta * 1.25 < currDelta) // we're slowing down, stop counting - firstDeltaSwitch = false; // if we're speeding up, this stays true and we keep counting island size. - - islandSize = 1; - } + if (islandSize < 7) + islandSize++; // island is still progressing, count size. } - else if (prevDelta > 1.25 * currDelta) // we want to be speeding up. + else { - // Begin counting island until we change speed again. - firstDeltaSwitch = true; + if (Previous[i - 1].BaseObject is Slider) // bpm change is into slider, this is easy acc window + effectiveRatio *= 0.125; + + if (Previous[i].BaseObject is Slider) // bpm change was from a slider, this is easier typically than circle -> circle + effectiveRatio *= 0.25; + + if (previousIslandSize == islandSize) // repeated island size (ex: triplet -> triplet) + effectiveRatio *= 0.25; + + if (previousIslandSize % 2 == islandSize % 2) // repeated island polartiy (2 -> 4, 3 -> 5) + effectiveRatio *= 0.50; + + if (lastDelta > prevDelta + 10 && prevDelta > currDelta + 10) // previous increase happened a note ago, 1/1->1/2-1/4, dont want to buff this. + effectiveRatio *= 0.125; + + rhythmComplexitySum += Math.Sqrt(effectiveRatio * startRatio) * currHistoricalDecay * Math.Sqrt(4 + islandSize) / 2 * Math.Sqrt(4 + previousIslandSize) / 2; + startRatio = effectiveRatio; + + previousIslandSize = islandSize; // log the last island size. + + if (prevDelta * 1.25 < currDelta) // we're slowing down, stop counting + firstDeltaSwitch = false; // if we're speeding up, this stays true and we keep counting island size. + islandSize = 1; } } + else if (prevDelta > 1.25 * currDelta) // we want to be speeding up. + { + // Begin counting island until we change speed again. + firstDeltaSwitch = true; + startRatio = effectiveRatio; + islandSize = 1; + } } return Math.Sqrt(4 + rhythmComplexitySum * rhythm_multiplier) / 2; //produces multiplier that can be applied to strain. range [1, infinity) (not really though) From 9d85beddbe5c45fa3985a6994f9dab05267eab9f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 15 Dec 2021 11:16:37 +0900 Subject: [PATCH 118/146] Fix null reference in some tests due to missing realm context factory --- osu.Game/Skinning/Skin.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/osu.Game/Skinning/Skin.cs b/osu.Game/Skinning/Skin.cs index ee92b6b40a..d606d94b97 100644 --- a/osu.Game/Skinning/Skin.cs +++ b/osu.Game/Skinning/Skin.cs @@ -43,7 +43,11 @@ namespace osu.Game.Skinning protected Skin(SkinInfo skin, IStorageResourceProvider resources, [CanBeNull] Stream configurationStream = null) { - SkinInfo = skin.ToLive(resources.RealmContextFactory); + SkinInfo = resources?.RealmContextFactory != null + ? skin.ToLive(resources.RealmContextFactory) + // This path should only be used in some tests. + : skin.ToLiveUnmanaged(); + this.resources = resources; configurationStream ??= getConfigurationStream(); From 0c11fe741309f8b990f4d85b69080dd9beb53f55 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 15 Dec 2021 12:45:09 +0900 Subject: [PATCH 119/146] Fix toast popups spamming samples when adjusting osu!mania scroll speed during gameplay Not the most robust of fixes, but as per the reasoning described in the issue thread, a proper fix will take considerably more effort. This intends to fix the issue first and foremost, as it sounds so bad I'd want to mute my sound before adjusting currently. Closes #15718. --- osu.Game/Overlays/OSD/TrackedSettingToast.cs | 24 ++++++++++++++++++++ osu.Game/Overlays/OnScreenDisplay.cs | 2 +- 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/OSD/TrackedSettingToast.cs b/osu.Game/Overlays/OSD/TrackedSettingToast.cs index 51214fe460..9939ba024e 100644 --- a/osu.Game/Overlays/OSD/TrackedSettingToast.cs +++ b/osu.Game/Overlays/OSD/TrackedSettingToast.cs @@ -5,12 +5,14 @@ using System; using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Audio.Sample; +using osu.Framework.Bindables; using osu.Framework.Configuration.Tracking; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; +using osu.Game.Configuration; using osu.Game.Graphics; using osuTK; using osuTK.Graphics; @@ -28,6 +30,8 @@ namespace osu.Game.Overlays.OSD private Sample sampleOff; private Sample sampleChange; + private Bindable lastPlaybackTime; + public TrackedSettingToast(SettingDescription description) : base(description.Name, description.Value, description.Shortcut) { @@ -75,10 +79,28 @@ namespace osu.Game.Overlays.OSD optionLights.Add(new OptionLight { Glowing = i == selectedOption }); } + [Resolved] + private SessionStatics statics { get; set; } + protected override void LoadComplete() { base.LoadComplete(); + playSound(); + } + + private void playSound() + { + // This debounce code roughly follows what we're using in HoverSampleDebounceComponent. + // We're sharing the existing static for hover sounds because it doesn't really matter if they block each other. + // This is a simple solution, but if this ever becomes a problem (or other performance issues arise), + // the whole toast system should be rewritten to avoid recreating this drawable each time a value changes. + lastPlaybackTime = statics.GetBindable(Static.LastHoverSoundPlaybackTime); + + bool enoughTimePassedSinceLastPlayback = !lastPlaybackTime.Value.HasValue || Time.Current - lastPlaybackTime.Value >= OsuGameBase.SAMPLE_DEBOUNCE_TIME; + + if (!enoughTimePassedSinceLastPlayback) return; + if (optionCount == 1) { if (selectedOption == 0) @@ -93,6 +115,8 @@ namespace osu.Game.Overlays.OSD sampleChange.Frequency.Value = 1 + (double)selectedOption / (optionCount - 1) * 0.25f; sampleChange.Play(); } + + lastPlaybackTime.Value = Time.Current; } [BackgroundDependencyLoader] diff --git a/osu.Game/Overlays/OnScreenDisplay.cs b/osu.Game/Overlays/OnScreenDisplay.cs index be9d3cd794..6b3696ced9 100644 --- a/osu.Game/Overlays/OnScreenDisplay.cs +++ b/osu.Game/Overlays/OnScreenDisplay.cs @@ -101,7 +101,7 @@ namespace osu.Game.Overlays DisplayTemporarily(box); }); - private void displayTrackedSettingChange(SettingDescription description) => Display(new TrackedSettingToast(description)); + private void displayTrackedSettingChange(SettingDescription description) => Scheduler.AddOnce(Display, new TrackedSettingToast(description)); private TransformSequence fadeIn; private ScheduledDelegate fadeOut; From 828072bceaad5cb10ae39abdec82148136707fa0 Mon Sep 17 00:00:00 2001 From: JamesTheGeek Date: Tue, 14 Dec 2021 23:23:11 -0500 Subject: [PATCH 120/146] Fix issue #15869 The taiko-slider is not included in `Playfield`, so it doesn't get hidden when calling `drawableRuleSet.Playfield.Hide()`. Calling `drawableRuleSet.Hide()` hides the taiko-slider, in addition to the rest of the `Playfield`. --- osu.Game/Rulesets/Mods/ModCinema.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Rulesets/Mods/ModCinema.cs b/osu.Game/Rulesets/Mods/ModCinema.cs index c78088ba2d..f28ef1edeb 100644 --- a/osu.Game/Rulesets/Mods/ModCinema.cs +++ b/osu.Game/Rulesets/Mods/ModCinema.cs @@ -17,8 +17,8 @@ namespace osu.Game.Rulesets.Mods drawableRuleset.SetReplayScore(CreateReplayScore(drawableRuleset.Beatmap, drawableRuleset.Mods)); // AlwaysPresent required for hitsounds - drawableRuleset.Playfield.AlwaysPresent = true; - drawableRuleset.Playfield.Hide(); + drawableRuleset.AlwaysPresent = true; + drawableRuleset.Hide(); } } From b326ccc1968f5ff757ece4dd2806c878a2724a38 Mon Sep 17 00:00:00 2001 From: Susko3 <16479013+Susko3@users.noreply.github.com> Date: Wed, 15 Dec 2021 07:13:24 +0100 Subject: [PATCH 121/146] Move logic to framework and update all usages --- osu.Game/Graphics/UserInterface/OsuNumberBox.cs | 4 +++- osu.Game/Overlays/Settings/SettingsNumberBox.cs | 3 ++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/OsuNumberBox.cs b/osu.Game/Graphics/UserInterface/OsuNumberBox.cs index 36288c745a..3d565a4464 100644 --- a/osu.Game/Graphics/UserInterface/OsuNumberBox.cs +++ b/osu.Game/Graphics/UserInterface/OsuNumberBox.cs @@ -1,10 +1,12 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using osu.Framework.Extensions; + namespace osu.Game.Graphics.UserInterface { public class OsuNumberBox : OsuTextBox { - protected override bool CanAddCharacter(char character) => char.IsNumber(character); + protected override bool CanAddCharacter(char character) => character.IsAsciiDigit(); } } diff --git a/osu.Game/Overlays/Settings/SettingsNumberBox.cs b/osu.Game/Overlays/Settings/SettingsNumberBox.cs index 0a4949f8b6..cc4446033a 100644 --- a/osu.Game/Overlays/Settings/SettingsNumberBox.cs +++ b/osu.Game/Overlays/Settings/SettingsNumberBox.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Bindables; +using osu.Framework.Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.UserInterface; @@ -67,7 +68,7 @@ namespace osu.Game.Overlays.Settings private class OutlinedNumberBox : OutlinedTextBox { - protected override bool CanAddCharacter(char character) => character >= '0' && character <= '9'; + protected override bool CanAddCharacter(char character) => character.IsAsciiDigit(); public new void NotifyInputError() => base.NotifyInputError(); } From e9187cc3cf3192b3559b23b7c4cf5a60d4b1832a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 15 Dec 2021 16:13:12 +0900 Subject: [PATCH 122/146] Add failing test showing expanded state being unexpectedly lost --- .../Visual/Beatmaps/TestSceneBeatmapCard.cs | 37 ++++++++++++++----- 1 file changed, 27 insertions(+), 10 deletions(-) diff --git a/osu.Game.Tests/Visual/Beatmaps/TestSceneBeatmapCard.cs b/osu.Game.Tests/Visual/Beatmaps/TestSceneBeatmapCard.cs index f835d21603..0b9857486a 100644 --- a/osu.Game.Tests/Visual/Beatmaps/TestSceneBeatmapCard.cs +++ b/osu.Game.Tests/Visual/Beatmaps/TestSceneBeatmapCard.cs @@ -6,12 +6,12 @@ using System.Collections.Generic; using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; -using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Testing; using osu.Game.Beatmaps; +using osu.Game.Beatmaps.Drawables; using osu.Game.Beatmaps.Drawables.Cards; using osu.Game.Graphics.Containers; using osu.Game.Online.API; @@ -19,11 +19,10 @@ using osu.Game.Online.API.Requests; using osu.Game.Online.API.Requests.Responses; using osu.Game.Overlays; using osuTK; -using APIUser = osu.Game.Online.API.Requests.Responses.APIUser; namespace osu.Game.Tests.Visual.Beatmaps { - public class TestSceneBeatmapCard : OsuTestScene + public class TestSceneBeatmapCard : OsuManualInputManagerTestScene { /// /// All cards on this scene use a common online ID to ensure that map download, preview tracks, etc. can be tested manually with online sources. @@ -253,14 +252,32 @@ namespace osu.Game.Tests.Visual.Beatmaps public void TestNormal() { createTestCase(beatmapSetInfo => new BeatmapCard(beatmapSetInfo)); + } - AddToggleStep("toggle expanded state", expanded => - { - var card = this.ChildrenOfType().Last(); - if (!card.Expanded.Disabled) - card.Expanded.Value = expanded; - }); - AddToggleStep("disable/enable expansion", disabled => this.ChildrenOfType().ForEach(card => card.Expanded.Disabled = disabled)); + [Test] + public void TestHoverState() + { + AddStep("create cards", () => Child = createContent(OverlayColourScheme.Blue, s => new BeatmapCard(s))); + + AddStep("Hover card", () => InputManager.MoveMouseTo(firstCard())); + AddWaitStep("wait for potential state change", 5); + AddAssert("card is not expanded", () => !firstCard().Expanded.Value); + + AddStep("Hover spectrum display", () => InputManager.MoveMouseTo(firstCard().ChildrenOfType().Single())); + AddUntilStep("card is expanded", () => firstCard().Expanded.Value); + + AddStep("Hover difficulty content", () => InputManager.MoveMouseTo(firstCard().ChildrenOfType().Single())); + AddWaitStep("wait for potential state change", 5); + AddAssert("card is still expanded", () => firstCard().Expanded.Value); + + AddStep("Hover main content again", () => InputManager.MoveMouseTo(firstCard())); + AddWaitStep("wait for potential state change", 5); + AddAssert("card is still expanded", () => firstCard().Expanded.Value); + + AddStep("Hover away", () => InputManager.MoveMouseTo(this.ChildrenOfType().Last())); + AddUntilStep("card is not expanded", () => !firstCard().Expanded.Value); + + BeatmapCard firstCard() => this.ChildrenOfType().First(); } } } From 41e6c24dad8750edd2115c12ecd6857252036b46 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 15 Dec 2021 15:51:47 +0900 Subject: [PATCH 123/146] Expose `Expanded` state of `BeatmapCardContent` as read-only bindable This is just to reduce complexity of these interactions by ensuring that the expanded state can only be changed by the class itself. --- .../Beatmaps/Drawables/Cards/BeatmapCardContent.cs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardContent.cs b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardContent.cs index 681f09c658..e353e61b71 100644 --- a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardContent.cs +++ b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardContent.cs @@ -31,7 +31,9 @@ namespace osu.Game.Beatmaps.Drawables.Cards set => dropdownScroll.Child = value; } - public Bindable Expanded { get; } = new BindableBool(); + public IBindable Expanded => expanded; + + private readonly BindableBool expanded = new BindableBool(); private readonly Box background; private readonly Container content; @@ -128,7 +130,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards scheduledExpandedChange = Scheduler.AddDelayed(() => { if (!Expanded.Disabled) - Expanded.Value = true; + expanded.Value = true; }, 100); } @@ -141,7 +143,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards scheduledExpandedChange = Scheduler.AddDelayed(() => { if (!Expanded.Disabled) - Expanded.Value = false; + expanded.Value = false; }, 500); } @@ -154,7 +156,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards return; scheduledExpandedChange?.Cancel(); - Expanded.Value = false; + expanded.Value = false; } private void keep() @@ -163,7 +165,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards return; scheduledExpandedChange?.Cancel(); - Expanded.Value = true; + expanded.Value = true; } private void updateState() From ef4ab74565a843d23da2e05bc01a7ace7478a353 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 15 Dec 2021 16:19:47 +0900 Subject: [PATCH 124/146] Also only expose `Expanded` state of `BeatmapCard` as read-only --- osu.Game/Beatmaps/Drawables/Cards/BeatmapCard.cs | 6 ++++-- osu.Game/Screens/Play/SoloSpectator.cs | 5 +---- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCard.cs b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCard.cs index d93ac841ab..435a8227bd 100644 --- a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCard.cs +++ b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCard.cs @@ -33,7 +33,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards public const float TRANSITION_DURATION = 400; public const float CORNER_RADIUS = 10; - public Bindable Expanded { get; } = new BindableBool(); + public IBindable Expanded { get; } private const float width = 408; private const float height = 100; @@ -64,9 +64,11 @@ namespace osu.Game.Beatmaps.Drawables.Cards [Resolved] private OverlayColourProvider colourProvider { get; set; } = null!; - public BeatmapCard(APIBeatmapSet beatmapSet) + public BeatmapCard(APIBeatmapSet beatmapSet, bool allowExpansion = true) : base(HoverSampleSet.Submit) { + Expanded = new BindableBool { Disabled = !allowExpansion }; + this.beatmapSet = beatmapSet; favouriteState = new Bindable(new BeatmapSetFavouriteState(beatmapSet.HasFavourited, beatmapSet.FavouriteCount)); downloadTracker = new BeatmapDownloadTracker(beatmapSet); diff --git a/osu.Game/Screens/Play/SoloSpectator.cs b/osu.Game/Screens/Play/SoloSpectator.cs index 3918dbe8fc..ba5663bfa3 100644 --- a/osu.Game/Screens/Play/SoloSpectator.cs +++ b/osu.Game/Screens/Play/SoloSpectator.cs @@ -228,10 +228,7 @@ namespace osu.Game.Screens.Play onlineBeatmapRequest.Success += beatmapSet => Schedule(() => { this.beatmapSet = beatmapSet; - beatmapPanelContainer.Child = new BeatmapCard(this.beatmapSet) - { - Expanded = { Disabled = true } - }; + beatmapPanelContainer.Child = new BeatmapCard(this.beatmapSet, allowExpansion: false); checkForAutomaticDownload(); }); From 7a9db22c5240a600b87a40b74cc6416cc0d9122f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 15 Dec 2021 16:02:43 +0900 Subject: [PATCH 125/146] Tidy up method naming and structure for expanded state changes --- .../Beatmaps/Drawables/Cards/BeatmapCard.cs | 6 +-- .../Drawables/Cards/BeatmapCardContent.cs | 52 +++++-------------- 2 files changed, 15 insertions(+), 43 deletions(-) diff --git a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCard.cs b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCard.cs index 435a8227bd..2e39fafa05 100644 --- a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCard.cs +++ b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCard.cs @@ -284,7 +284,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards { Hovered = _ => { - content.ScheduleShow(); + content.ExpandAfterDelay(); return false; }, Unhovered = _ => @@ -292,7 +292,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards // This hide should only trigger if the expanded content has not shown yet. // ie. if the user has not shown intent to want to see it (quickly moved over the info row area). if (!Expanded.Value) - content.ScheduleHide(); + content.CollapseAfterDelay(); } } } @@ -368,7 +368,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards protected override void OnHoverLost(HoverLostEvent e) { - content.ScheduleHide(); + content.CollapseAfterDelay(); updateState(); base.OnHoverLost(e); diff --git a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardContent.cs b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardContent.cs index e353e61b71..148372786a 100644 --- a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardContent.cs +++ b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardContent.cs @@ -56,7 +56,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards AutoSizeAxes = Axes.Y, CornerRadius = BeatmapCard.CORNER_RADIUS, Masking = true, - Unhovered = _ => checkForHide(), + Unhovered = _ => collapseIfNotHovered(), Children = new Drawable[] { background = new Box @@ -78,10 +78,10 @@ namespace osu.Game.Beatmaps.Drawables.Cards Alpha = 0, Hovered = _ => { - keep(); + queueExpandedStateChange(true); return true; }, - Unhovered = _ => checkForHide(), + Unhovered = _ => collapseIfNotHovered(), Child = dropdownScroll = new ExpandedContentScrollContainer { RelativeSizeAxes = Axes.X, @@ -121,51 +121,23 @@ namespace osu.Game.Beatmaps.Drawables.Cards private ScheduledDelegate? scheduledExpandedChange; - public void ScheduleShow() - { - scheduledExpandedChange?.Cancel(); - if (Expanded.Disabled || Expanded.Value) - return; + public void ExpandAfterDelay() => queueExpandedStateChange(true, 100); - scheduledExpandedChange = Scheduler.AddDelayed(() => - { - if (!Expanded.Disabled) - expanded.Value = true; - }, 100); + public void CollapseAfterDelay() => queueExpandedStateChange(false, 500); + + private void collapseIfNotHovered() + { + if (!content.IsHovered && !dropdownContent.IsHovered) + queueExpandedStateChange(false); } - public void ScheduleHide() - { - scheduledExpandedChange?.Cancel(); - if (Expanded.Disabled || !Expanded.Value) - return; - - scheduledExpandedChange = Scheduler.AddDelayed(() => - { - if (!Expanded.Disabled) - expanded.Value = false; - }, 500); - } - - private void checkForHide() - { - if (Expanded.Disabled) - return; - - if (content.IsHovered || dropdownContent.IsHovered) - return; - - scheduledExpandedChange?.Cancel(); - expanded.Value = false; - } - - private void keep() + private void queueExpandedStateChange(bool newState, int delay = 0) { if (Expanded.Disabled) return; scheduledExpandedChange?.Cancel(); - expanded.Value = true; + scheduledExpandedChange = Scheduler.AddDelayed(() => expanded.Value = newState, delay); } private void updateState() From 94d1a2aacae0a6472e06eeb004004a984feb69c4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 15 Dec 2021 16:37:44 +0900 Subject: [PATCH 126/146] Remove unnecessary collapse call from `BeatmapCard` This is already handled at the `BeatmapCardContent` level. This call actually causes the buggy behaviour reported in https://github.com/ppy/osu/discussions/16085. --- osu.Game/Beatmaps/Drawables/Cards/BeatmapCard.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCard.cs b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCard.cs index 2e39fafa05..be7119be36 100644 --- a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCard.cs +++ b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCard.cs @@ -368,8 +368,6 @@ namespace osu.Game.Beatmaps.Drawables.Cards protected override void OnHoverLost(HoverLostEvent e) { - content.CollapseAfterDelay(); - updateState(); base.OnHoverLost(e); } From 6a1f535257c49720f9f81ca80d9942d44889456d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 15 Dec 2021 16:38:19 +0900 Subject: [PATCH 127/146] Refactor cancellation of expand to be more explicit --- osu.Game/Beatmaps/Drawables/Cards/BeatmapCard.cs | 6 +++--- osu.Game/Beatmaps/Drawables/Cards/BeatmapCardContent.cs | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCard.cs b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCard.cs index be7119be36..1e24501426 100644 --- a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCard.cs +++ b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCard.cs @@ -289,10 +289,10 @@ namespace osu.Game.Beatmaps.Drawables.Cards }, Unhovered = _ => { - // This hide should only trigger if the expanded content has not shown yet. - // ie. if the user has not shown intent to want to see it (quickly moved over the info row area). + // Handles the case where a user has not shown explicit intent to view expanded info. + // ie. quickly moved over the info row area but didn't remain within it. if (!Expanded.Value) - content.CollapseAfterDelay(); + content.CancelExpand(); } } } diff --git a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardContent.cs b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardContent.cs index 148372786a..0739f7328f 100644 --- a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardContent.cs +++ b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardContent.cs @@ -123,7 +123,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards public void ExpandAfterDelay() => queueExpandedStateChange(true, 100); - public void CollapseAfterDelay() => queueExpandedStateChange(false, 500); + public void CancelExpand() => scheduledExpandedChange?.Cancel(); private void collapseIfNotHovered() { From ad430a627701afa2fcd7c538b99e232d4f54a693 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 15 Dec 2021 16:44:58 +0900 Subject: [PATCH 128/146] Centralise hover state handling (and fix back-to-front conditionals) --- .../Beatmaps/Drawables/Cards/BeatmapCardContent.cs | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardContent.cs b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardContent.cs index 0739f7328f..286e03e700 100644 --- a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardContent.cs +++ b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardContent.cs @@ -56,7 +56,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards AutoSizeAxes = Axes.Y, CornerRadius = BeatmapCard.CORNER_RADIUS, Masking = true, - Unhovered = _ => collapseIfNotHovered(), + Unhovered = _ => updateFromHoverChange(), Children = new Drawable[] { background = new Box @@ -78,10 +78,10 @@ namespace osu.Game.Beatmaps.Drawables.Cards Alpha = 0, Hovered = _ => { - queueExpandedStateChange(true); + updateFromHoverChange(); return true; }, - Unhovered = _ => collapseIfNotHovered(), + Unhovered = _ => updateFromHoverChange(), Child = dropdownScroll = new ExpandedContentScrollContainer { RelativeSizeAxes = Axes.X, @@ -125,11 +125,8 @@ namespace osu.Game.Beatmaps.Drawables.Cards public void CancelExpand() => scheduledExpandedChange?.Cancel(); - private void collapseIfNotHovered() - { - if (!content.IsHovered && !dropdownContent.IsHovered) - queueExpandedStateChange(false); - } + private void updateFromHoverChange() => + queueExpandedStateChange(content.IsHovered || dropdownContent.IsHovered, 100); private void queueExpandedStateChange(bool newState, int delay = 0) { From 42f14667a360a16e95197cd2d2635bb9e4026479 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 15 Dec 2021 16:50:55 +0900 Subject: [PATCH 129/146] 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 0c922c09ac..5e5e110dfb 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 adb25f46fe..bc429f9af1 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 db5d9af865..1ae9aa3dc5 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -60,7 +60,7 @@ - + @@ -83,7 +83,7 @@ - + From 740a6f16c79a606652649fc9e71cb607bfb343d9 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 15 Dec 2021 16:52:50 +0900 Subject: [PATCH 130/146] Fix exception when updating the room's visual playlist --- .../Multiplayer/TestSceneMultiplayer.cs | 43 +++++++++++++++++-- .../Match/Playlist/MultiplayerPlaylist.cs | 6 ++- 2 files changed, 45 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs index bc2902480d..2ea7f2541f 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs @@ -30,6 +30,7 @@ using osu.Game.Screens.OnlinePlay.Lounge.Components; using osu.Game.Screens.OnlinePlay.Match; using osu.Game.Screens.OnlinePlay.Multiplayer; using osu.Game.Screens.OnlinePlay.Multiplayer.Match; +using osu.Game.Screens.OnlinePlay.Multiplayer.Match.Playlist; using osu.Game.Screens.Play; using osu.Game.Screens.Ranking; using osu.Game.Screens.Spectate; @@ -594,9 +595,7 @@ namespace osu.Game.Tests.Visual.Multiplayer } }); - pressReadyButton(); - pressReadyButton(); - AddUntilStep("wait for player", () => multiplayerScreenStack.CurrentScreen is Player); + enterGameplay(); // Gameplay runs in real-time, so we need to incrementally check if gameplay has finished in order to not time out. for (double i = 1000; i < TestResources.QUICK_BEATMAP_LENGTH; i += 1000) @@ -656,6 +655,44 @@ namespace osu.Game.Tests.Visual.Multiplayer }); } + [Test] + public void TestItemAddedAndDeletedByOtherUserDuringGameplay() + { + createRoom(() => new Room + { + Name = { Value = "Test Room" }, + QueueMode = { Value = QueueMode.AllPlayers }, + Playlist = + { + new PlaylistItem + { + Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.RulesetID == 0)).BeatmapInfo }, + Ruleset = { Value = new OsuRuleset().RulesetInfo }, + } + } + }); + + enterGameplay(); + + AddStep("join other user", () => client.AddUser(new APIUser { Id = 1234 })); + AddStep("add item as other user", () => client.AddUserPlaylistItem(1234, new MultiplayerPlaylistItem(new PlaylistItem + { + BeatmapID = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.RulesetID == 0)).BeatmapInfo.OnlineID ?? -1 + }))); + AddStep("delete item as other user", () => client.RemoveUserPlaylistItem(1234, 2)); + + AddUntilStep("wait for item to be deleted", () => client.Room?.Playlist.Count == 1); + AddStep("exit gameplay as initial user", () => multiplayerScreenStack.MultiplayerScreen.MakeCurrent()); + AddUntilStep("queue is empty", () => this.ChildrenOfType().Single().Items.Count == 0); + } + + private void enterGameplay() + { + pressReadyButton(); + pressReadyButton(); + AddUntilStep("wait for player", () => multiplayerScreenStack.CurrentScreen is Player); + } + private ReadyButton readyButton => this.ChildrenOfType().Single(); private void pressReadyButton() diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerPlaylist.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerPlaylist.cs index 4971489769..7b90532cce 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerPlaylist.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerPlaylist.cs @@ -121,7 +121,11 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match.Playlist private void addItemToLists(MultiplayerPlaylistItem item) { - var apiItem = Playlist.Single(i => i.ID == item.ID); + var apiItem = Playlist.SingleOrDefault(i => i.ID == item.ID); + + // Item could have been removed from the playlist while the local player was in gameplay. + if (apiItem == null) + return; if (item.Expired) historyList.Items.Add(apiItem); From 694ee687250b3c910154ab2d83a927d6c061bdf3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 15 Dec 2021 16:59:33 +0900 Subject: [PATCH 131/146] Update resources --- osu.Android.props | 2 +- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index 5e5e110dfb..1131203a95 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -51,7 +51,7 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index bc429f9af1..5a0c999fb0 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -37,7 +37,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index 1ae9aa3dc5..27ac1bf647 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -61,7 +61,7 @@ - + From d22e1b90010ea570f03f4aad8450f72bce2d02e9 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 15 Dec 2021 16:59:34 +0900 Subject: [PATCH 132/146] Add another until step to guard against async test issues --- osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs index 2ea7f2541f..1d951c257c 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs @@ -679,9 +679,12 @@ namespace osu.Game.Tests.Visual.Multiplayer { BeatmapID = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.RulesetID == 0)).BeatmapInfo.OnlineID ?? -1 }))); - AddStep("delete item as other user", () => client.RemoveUserPlaylistItem(1234, 2)); - AddUntilStep("wait for item to be deleted", () => client.Room?.Playlist.Count == 1); + AddUntilStep("item arrived in playlist", () => client.Room?.Playlist.Count == 2); + + AddStep("delete item as other user", () => client.RemoveUserPlaylistItem(1234, 2)); + AddUntilStep("item removed from playlist", () => client.Room?.Playlist.Count == 1); + AddStep("exit gameplay as initial user", () => multiplayerScreenStack.MultiplayerScreen.MakeCurrent()); AddUntilStep("queue is empty", () => this.ChildrenOfType().Single().Items.Count == 0); } From 39a0a2113219ef3319da9dd4e6e0c023b5f0ce52 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 15 Dec 2021 17:30:09 +0900 Subject: [PATCH 133/146] Add test coverage of same scenario without deletion --- .../Multiplayer/TestSceneMultiplayer.cs | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs index 1d951c257c..ed8d50589f 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs @@ -655,6 +655,37 @@ namespace osu.Game.Tests.Visual.Multiplayer }); } + [Test] + public void TestItemAddedByOtherUserDuringGameplay() + { + createRoom(() => new Room + { + Name = { Value = "Test Room" }, + QueueMode = { Value = QueueMode.AllPlayers }, + Playlist = + { + new PlaylistItem + { + Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.RulesetID == 0)).BeatmapInfo }, + Ruleset = { Value = new OsuRuleset().RulesetInfo }, + } + } + }); + + enterGameplay(); + + AddStep("join other user", () => client.AddUser(new APIUser { Id = 1234 })); + AddStep("add item as other user", () => client.AddUserPlaylistItem(1234, new MultiplayerPlaylistItem(new PlaylistItem + { + BeatmapID = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.RulesetID == 0)).BeatmapInfo.OnlineID ?? -1 + }))); + + AddUntilStep("item arrived in playlist", () => client.Room?.Playlist.Count == 2); + + AddStep("exit gameplay as initial user", () => multiplayerScreenStack.MultiplayerScreen.MakeCurrent()); + AddUntilStep("queue contains item", () => this.ChildrenOfType().Single().Items.Single().ID == 2); + } + [Test] public void TestItemAddedAndDeletedByOtherUserDuringGameplay() { From 18d7b7920798bec5097f30b1ff85f413a4adf098 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 15 Dec 2021 17:37:39 +0900 Subject: [PATCH 134/146] Don't reset spectating state if gameplay is finished --- .../Multiplayer/TestSceneMultiplayer.cs | 91 ++++++++++++++++++- .../Visual/TestMultiplayerScreenStack.cs | 2 +- .../Spectate/MultiSpectatorScreen.cs | 9 +- 3 files changed, 96 insertions(+), 6 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs index bc2902480d..a2de391aec 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs @@ -30,6 +30,7 @@ using osu.Game.Screens.OnlinePlay.Lounge.Components; using osu.Game.Screens.OnlinePlay.Match; using osu.Game.Screens.OnlinePlay.Multiplayer; using osu.Game.Screens.OnlinePlay.Multiplayer.Match; +using osu.Game.Screens.OnlinePlay.Multiplayer.Spectate; using osu.Game.Screens.Play; using osu.Game.Screens.Ranking; using osu.Game.Screens.Spectate; @@ -656,23 +657,107 @@ namespace osu.Game.Tests.Visual.Multiplayer }); } + [Test] + public void TestSpectatingStateResetOnBackButtonDuringGameplay() + { + createRoom(() => new Room + { + Name = { Value = "Test Room" }, + QueueMode = { Value = QueueMode.AllPlayers }, + Playlist = + { + new PlaylistItem + { + Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.RulesetID == 0)).BeatmapInfo }, + Ruleset = { Value = new OsuRuleset().RulesetInfo }, + } + } + }); + + AddStep("set spectating state", () => client.ChangeUserState(API.LocalUser.Value.OnlineID, MultiplayerUserState.Spectating)); + AddUntilStep("state set to spectating", () => client.LocalUser?.State == MultiplayerUserState.Spectating); + + AddStep("join other user", () => client.AddUser(new APIUser { Id = 1234 })); + AddStep("set other user ready", () => client.ChangeUserState(1234, MultiplayerUserState.Ready)); + + pressReadyButton(1234); + AddUntilStep("wait for gameplay", () => (multiplayerScreenStack.CurrentScreen as MultiSpectatorScreen)?.IsLoaded == true); + + AddStep("press back button and exit", () => + { + multiplayerScreenStack.OnBackButton(); + multiplayerScreenStack.Exit(); + }); + + AddUntilStep("wait for return to match subscreen", () => multiplayerScreenStack.MultiplayerScreen.IsCurrentScreen()); + AddUntilStep("user state is idle", () => client.LocalUser?.State == MultiplayerUserState.Idle); + } + + [Test] + public void TestSpectatingStateNotResetOnBackButtonOutsideOfGameplay() + { + createRoom(() => new Room + { + Name = { Value = "Test Room" }, + QueueMode = { Value = QueueMode.AllPlayers }, + Playlist = + { + new PlaylistItem + { + Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.RulesetID == 0)).BeatmapInfo }, + Ruleset = { Value = new OsuRuleset().RulesetInfo }, + } + } + }); + + AddStep("set spectating state", () => client.ChangeUserState(API.LocalUser.Value.OnlineID, MultiplayerUserState.Spectating)); + AddUntilStep("state set to spectating", () => client.LocalUser?.State == MultiplayerUserState.Spectating); + + AddStep("join other user", () => client.AddUser(new APIUser { Id = 1234 })); + AddStep("set other user ready", () => client.ChangeUserState(1234, MultiplayerUserState.Ready)); + + pressReadyButton(1234); + AddUntilStep("wait for gameplay", () => (multiplayerScreenStack.CurrentScreen as MultiSpectatorScreen)?.IsLoaded == true); + AddStep("set other user loaded", () => client.ChangeUserState(1234, MultiplayerUserState.Loaded)); + AddStep("set other user finished play", () => client.ChangeUserState(1234, MultiplayerUserState.FinishedPlay)); + + AddStep("press back button and exit", () => + { + multiplayerScreenStack.OnBackButton(); + multiplayerScreenStack.Exit(); + }); + + AddUntilStep("wait for return to match subscreen", () => multiplayerScreenStack.MultiplayerScreen.IsCurrentScreen()); + AddWaitStep("wait for possible state change", 5); + AddUntilStep("user state is spectating", () => client.LocalUser?.State == MultiplayerUserState.Spectating); + } + + private void enterGameplay() + { + pressReadyButton(); + pressReadyButton(); + AddUntilStep("wait for player", () => multiplayerScreenStack.CurrentScreen is Player); + } + private ReadyButton readyButton => this.ChildrenOfType().Single(); - private void pressReadyButton() + private void pressReadyButton(int? playingUserId = null) { AddUntilStep("wait for ready button to be enabled", () => readyButton.Enabled.Value); MultiplayerUserState lastState = MultiplayerUserState.Idle; + MultiplayerRoomUser user = null; AddStep("click ready button", () => { - lastState = client.LocalUser?.State ?? MultiplayerUserState.Idle; + user = playingUserId == null ? client.LocalUser : client.Room?.Users.Single(u => u.UserID == playingUserId); + lastState = user?.State ?? MultiplayerUserState.Idle; InputManager.MoveMouseTo(readyButton); InputManager.Click(MouseButton.Left); }); - AddUntilStep("wait for state change", () => client.LocalUser?.State != lastState); + AddUntilStep("wait for state change", () => user?.State != lastState); } private void createRoom(Func room) diff --git a/osu.Game.Tests/Visual/TestMultiplayerScreenStack.cs b/osu.Game.Tests/Visual/TestMultiplayerScreenStack.cs index 7f1171db1f..370f3bd0ae 100644 --- a/osu.Game.Tests/Visual/TestMultiplayerScreenStack.cs +++ b/osu.Game.Tests/Visual/TestMultiplayerScreenStack.cs @@ -61,7 +61,7 @@ namespace osu.Game.Tests.Visual ((DummyAPIAccess)api).HandleRequest = request => multiplayerScreen.RequestsHandler.HandleRequest(request, api.LocalUser.Value, game); } - public override bool OnBackButton() => multiplayerScreen.OnBackButton(); + public override bool OnBackButton() => (screenStack.CurrentScreen as OsuScreen)?.OnBackButton() ?? base.OnBackButton(); public override bool OnExiting(IScreen next) { diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs index 9ac64add9a..7350408eba 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Diagnostics; using System.Linq; using JetBrains.Annotations; using osu.Framework.Allocation; @@ -226,8 +227,12 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate public override bool OnBackButton() { - // On a manual exit, set the player state back to idle. - multiplayerClient.ChangeState(MultiplayerUserState.Idle); + Debug.Assert(multiplayerClient.Room != null); + + // On a manual exit, set the player back to idle unless gameplay has finished. + if (multiplayerClient.Room.State != MultiplayerRoomState.Open) + multiplayerClient.ChangeState(MultiplayerUserState.Idle); + return base.OnBackButton(); } } From c50206e25e285994df75c1927d5aa2889ce9d100 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 16 Dec 2021 14:11:38 +0900 Subject: [PATCH 135/146] Update a few more public facing usages of "lazer" --- CONTRIBUTING.md | 8 ++++---- README.md | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index e14be20642..ae2bdd2e82 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,6 +1,6 @@ # Contributing Guidelines -Thank you for showing interest in the development of osu!lazer! We aim to provide a good collaborating environment for everyone involved, and as such have decided to list some of the most important things to keep in mind in the process. The guidelines below have been chosen based on past experience. +Thank you for showing interest in the development of osu!. We aim to provide a good collaborating environment for everyone involved, and as such have decided to list some of the most important things to keep in mind in the process. The guidelines below have been chosen based on past experience. These are not "official rules" *per se*, but following them will help everyone deal with things in the most efficient manner. @@ -32,7 +32,7 @@ Issues, bug reports and feature suggestions are welcomed, though please keep in * **Provide more information when asked to do so.** - Sometimes when a bug is more elusive or complicated, none of the information listed above will pinpoint a concrete cause of the problem. In this case we will most likely ask you for additional info, such as a Windows Event Log dump or a copy of your local lazer database (`client.db`). Providing that information is beneficial to both parties - we can track down the problem better, and hopefully fix it for you at some point once we know where it is! + Sometimes when a bug is more elusive or complicated, none of the information listed above will pinpoint a concrete cause of the problem. In this case we will most likely ask you for additional info, such as a Windows Event Log dump or a copy of your local osu! database (`client.db`). Providing that information is beneficial to both parties - we can track down the problem better, and hopefully fix it for you at some point once we know where it is! * **When submitting a feature proposal, please describe it in the most understandable way you can.** @@ -54,7 +54,7 @@ Issues, bug reports and feature suggestions are welcomed, though please keep in We also welcome pull requests from unaffiliated contributors. The [issue tracker](https://github.com/ppy/osu/issues) should provide plenty of issues that you can work on; we also mark issues that we think would be good for newcomers with the [`good-first-issue`](https://github.com/ppy/osu/issues?q=is%3Aissue+is%3Aopen+label%3Agood-first-issue) label. -However, do keep in mind that the core team is committed to bringing osu!lazer up to par with stable first and foremost, so depending on what your contribution concerns, it might not be merged and released right away. Our approach to managing issues and their priorities is described [in the wiki](https://github.com/ppy/osu/wiki/Project-management). +However, do keep in mind that the core team is committed to bringing osu!(lazer) up to par with osu!(stable) first and foremost, so depending on what your contribution concerns, it might not be merged and released right away. Our approach to managing issues and their priorities is described [in the wiki](https://github.com/ppy/osu/wiki/Project-management). Here are some key things to note before jumping in: @@ -128,7 +128,7 @@ Here are some key things to note before jumping in: * **Don't mistake criticism of code for criticism of your person.** - As mentioned before, we are highly committed to quality when it comes to the lazer project. This means that contributions from less experienced community members can take multiple rounds of review to get to a mergeable state. We try our utmost best to never conflate a person with the code they authored, and to keep the discussion focused on the code at all times. Please consider our comments and requests a learning experience, and don't treat it as a personal attack. + As mentioned before, we are highly committed to quality when it comes to the osu! project. This means that contributions from less experienced community members can take multiple rounds of review to get to a mergeable state. We try our utmost best to never conflate a person with the code they authored, and to keep the discussion focused on the code at all times. Please consider our comments and requests a learning experience, and don't treat it as a personal attack. * **Feel free to reach out for help.** diff --git a/README.md b/README.md index 786ce2589d..24b70b2de6 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ A free-to-win rhythm game. Rhythm is just a *click* away! -The future of [osu!](https://osu.ppy.sh) and the beginning of an open era! Currently known by and released under the codename "*lazer*". As in sharper than cutting-edge. +The future of [osu!](https://osu.ppy.sh) and the beginning of an open era! Currently known by and released under the release codename "*lazer*". As in sharper than cutting-edge. ## Status From b3e83a47a4651442ddd499d66f63a7b96c6173bf Mon Sep 17 00:00:00 2001 From: Imad Dodin Date: Wed, 15 Dec 2021 21:34:59 -0800 Subject: [PATCH 136/146] Convert to Local Time in Date Tooltip --- osu.Game/Graphics/DateTooltip.cs | 6 ++++-- osu.Game/Graphics/DrawableDate.cs | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/osu.Game/Graphics/DateTooltip.cs b/osu.Game/Graphics/DateTooltip.cs index 3094f9cc2b..d5768b259a 100644 --- a/osu.Game/Graphics/DateTooltip.cs +++ b/osu.Game/Graphics/DateTooltip.cs @@ -65,8 +65,10 @@ namespace osu.Game.Graphics public void SetContent(DateTimeOffset date) { - dateText.Text = $"{date:d MMMM yyyy} "; - timeText.Text = $"{date:HH:mm:ss \"UTC\"z}"; + DateTimeOffset localDate = date.ToLocalTime(); + + dateText.Text = $"{localDate:d MMMM yyyy} "; + timeText.Text = $"{localDate:HH:mm:ss \"UTC\"z}"; } public void Move(Vector2 pos) => Position = pos; diff --git a/osu.Game/Graphics/DrawableDate.cs b/osu.Game/Graphics/DrawableDate.cs index 567a39b4f4..4605976692 100644 --- a/osu.Game/Graphics/DrawableDate.cs +++ b/osu.Game/Graphics/DrawableDate.cs @@ -22,7 +22,7 @@ namespace osu.Game.Graphics if (date == value) return; - date = value.ToLocalTime(); + date = value; if (LoadState >= LoadState.Ready) updateTime(); From a9dbcd92a1fc05a8bbb800f1f0c5a4c7c2f147f5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 16 Dec 2021 15:11:48 +0900 Subject: [PATCH 137/146] Split out unmanaged implementation of `RealmLive` into its own class --- osu.Game/Database/RealmLive.cs | 16 ++------ osu.Game/Database/RealmLiveUnmanaged.cs | 44 ++++++++++++++++++++++ osu.Game/Database/RealmObjectExtensions.cs | 4 +- 3 files changed, 49 insertions(+), 15 deletions(-) create mode 100644 osu.Game/Database/RealmLiveUnmanaged.cs diff --git a/osu.Game/Database/RealmLive.cs b/osu.Game/Database/RealmLive.cs index 4f7bdf93e4..90b8814c24 100644 --- a/osu.Game/Database/RealmLive.cs +++ b/osu.Game/Database/RealmLive.cs @@ -24,20 +24,16 @@ namespace osu.Game.Database /// private readonly T data; - private readonly RealmContextFactory? realmFactory; + private readonly RealmContextFactory realmFactory; /// /// Construct a new instance of live realm data. /// /// The realm data. /// The realm factory the data was sourced from. May be null for an unmanaged object. - public RealmLive(T data, RealmContextFactory? realmFactory) + public RealmLive(T data, RealmContextFactory realmFactory) { this.data = data; - - if (IsManaged && realmFactory == null) - throw new ArgumentException(@"Realm factory must be provided for a managed instance", nameof(realmFactory)); - this.realmFactory = realmFactory; ID = data.ID; @@ -55,9 +51,6 @@ namespace osu.Game.Database return; } - if (realmFactory == null) - throw new ArgumentException(@"Realm factory must be provided for a managed instance", nameof(realmFactory)); - using (var realm = realmFactory.CreateContext()) perform(realm.Find(ID)); } @@ -74,9 +67,6 @@ namespace osu.Game.Database if (!IsManaged) return perform(data); - if (realmFactory == null) - throw new ArgumentException(@"Realm factory must be provided for a managed instance", nameof(realmFactory)); - using (var realm = realmFactory.CreateContext()) return perform(realm.Find(ID)); } @@ -108,7 +98,7 @@ namespace osu.Game.Database if (!ThreadSafety.IsUpdateThread) throw new InvalidOperationException($"Can't use {nameof(Value)} on managed objects from non-update threads"); - return realmFactory!.Context.Find(ID); + return realmFactory.Context.Find(ID); } } diff --git a/osu.Game/Database/RealmLiveUnmanaged.cs b/osu.Game/Database/RealmLiveUnmanaged.cs new file mode 100644 index 0000000000..5a69898206 --- /dev/null +++ b/osu.Game/Database/RealmLiveUnmanaged.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 System; +using Realms; + +#nullable enable + +namespace osu.Game.Database +{ + /// + /// Provides a method of working with unmanaged realm objects. + /// Usually used for testing purposes where the instance is never required to be managed. + /// + /// The underlying object type. + public class RealmLiveUnmanaged : ILive where T : RealmObjectBase, IHasGuidPrimaryKey + { + /// + /// Construct a new instance of live realm data. + /// + /// The realm data. + public RealmLiveUnmanaged(T data) + { + Value = data; + } + + public bool Equals(ILive? other) => ID == other?.ID; + + public Guid ID => Value.ID; + + public void PerformRead(Action perform) => perform(Value); + + public TReturn PerformRead(Func perform) => perform(Value); + + public void PerformWrite(Action perform) => throw new InvalidOperationException(@"Can't perform writes on a non-managed underlying value"); + + public bool IsManaged => false; + + /// + /// The original live data used to create this instance. + /// + public T Value { get; } + } +} diff --git a/osu.Game/Database/RealmObjectExtensions.cs b/osu.Game/Database/RealmObjectExtensions.cs index c546a70fae..e5177823ba 100644 --- a/osu.Game/Database/RealmObjectExtensions.cs +++ b/osu.Game/Database/RealmObjectExtensions.cs @@ -56,13 +56,13 @@ namespace osu.Game.Database public static List> ToLiveUnmanaged(this IEnumerable realmList) where T : RealmObject, IHasGuidPrimaryKey { - return realmList.Select(l => new RealmLive(l, null)).Cast>().ToList(); + return realmList.Select(l => new RealmLiveUnmanaged(l)).Cast>().ToList(); } public static ILive ToLiveUnmanaged(this T realmObject) where T : RealmObject, IHasGuidPrimaryKey { - return new RealmLive(realmObject, null); + return new RealmLiveUnmanaged(realmObject); } public static List> ToLive(this IEnumerable realmList, RealmContextFactory realmContextFactory) From 488374b4a27c56b0f26061d55e3f7648236e3354 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 16 Dec 2021 16:41:47 +0900 Subject: [PATCH 138/146] Don't show multiplayer channels in chat overlay --- .../Visual/Online/TestSceneChatOverlay.cs | 19 +++++++++++++++++++ osu.Game/Overlays/ChatOverlay.cs | 14 +++++++++----- 2 files changed, 28 insertions(+), 5 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs index 9c65b2dc51..14f32df653 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs @@ -393,6 +393,25 @@ namespace osu.Game.Tests.Visual.Online channelManager.CurrentChannel.Value.Type == ChannelType.PM && channelManager.CurrentChannel.Value.Users.Single().Username == "some body"); } + [Test] + public void TestMultiplayerChannelIsNotShown() + { + Channel multiplayerChannel = null; + + AddStep("join multiplayer channel", () => channelManager.JoinChannel(multiplayerChannel = new Channel(new APIUser()) + { + Name = "#mp_1", + Type = ChannelType.Multiplayer, + })); + + AddAssert("channel joined", () => channelManager.JoinedChannels.Contains(multiplayerChannel)); + AddAssert("channel not present in overlay", () => !chatOverlay.TabMap.ContainsKey(multiplayerChannel)); + AddAssert("multiplayer channel is not current", () => channelManager.CurrentChannel.Value != multiplayerChannel); + + AddStep("leave channel", () => channelManager.LeaveChannel(multiplayerChannel)); + AddAssert("channel left", () => !channelManager.JoinedChannels.Contains(multiplayerChannel)); + } + private void pressChannelHotkey(int number) { var channelKey = Key.Number0 + number; diff --git a/osu.Game/Overlays/ChatOverlay.cs b/osu.Game/Overlays/ChatOverlay.cs index cc3ce63bf7..72473d5750 100644 --- a/osu.Game/Overlays/ChatOverlay.cs +++ b/osu.Game/Overlays/ChatOverlay.cs @@ -237,10 +237,7 @@ namespace osu.Game.Overlays Schedule(() => { // TODO: consider scheduling bindable callbacks to not perform when overlay is not present. - channelManager.JoinedChannels.CollectionChanged += joinedChannelsChanged; - - foreach (Channel channel in channelManager.JoinedChannels) - ChannelTabControl.AddChannel(channel); + channelManager.JoinedChannels.BindCollectionChanged(joinedChannelsChanged, true); channelManager.AvailableChannels.CollectionChanged += availableChannelsChanged; availableChannelsChanged(null, null); @@ -436,12 +433,19 @@ namespace osu.Game.Overlays { case NotifyCollectionChangedAction.Add: foreach (Channel channel in args.NewItems.Cast()) - ChannelTabControl.AddChannel(channel); + { + if (channel.Type != ChannelType.Multiplayer) + ChannelTabControl.AddChannel(channel); + } + break; case NotifyCollectionChangedAction.Remove: foreach (Channel channel in args.OldItems.Cast()) { + if (!ChannelTabControl.Items.Contains(channel)) + continue; + ChannelTabControl.RemoveChannel(channel); var loaded = loadedChannels.Find(c => c.Channel == channel); From 0eac655cff09722b5077ff48cc09702d541c0326 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 16 Dec 2021 18:21:48 +0900 Subject: [PATCH 139/146] Remove local screen change logging --- osu.Game/OsuGame.cs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index a35191613c..d2d6dad0c6 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -1149,16 +1149,11 @@ namespace osu.Game } } - private void screenPushed(IScreen lastScreen, IScreen newScreen) - { - ScreenChanged(lastScreen, newScreen); - Logger.Log($"Screen changed → {newScreen}"); - } + private void screenPushed(IScreen lastScreen, IScreen newScreen) => ScreenChanged(lastScreen, newScreen); private void screenExited(IScreen lastScreen, IScreen newScreen) { ScreenChanged(lastScreen, newScreen); - Logger.Log($"Screen changed ← {newScreen}"); if (newScreen == null) Exit(); From 434aa0367f4c58349bbc05258a02a99d233ef54f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 16 Dec 2021 18:25:28 +0900 Subject: [PATCH 140/146] Add back `.ToLocalTime()` call to `DrawableDate` This is required because the class is used in many other places that don't locally call it. --- osu.Game/Graphics/DrawableDate.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Graphics/DrawableDate.cs b/osu.Game/Graphics/DrawableDate.cs index 4605976692..567a39b4f4 100644 --- a/osu.Game/Graphics/DrawableDate.cs +++ b/osu.Game/Graphics/DrawableDate.cs @@ -22,7 +22,7 @@ namespace osu.Game.Graphics if (date == value) return; - date = value; + date = value.ToLocalTime(); if (LoadState >= LoadState.Ready) updateTime(); From 5ea081e89908e35993d29553218fe2254bda2c7b Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 16 Dec 2021 19:04:42 +0900 Subject: [PATCH 141/146] Test hyperdash generation in catch conversion tests --- .../CatchBeatmapConversionTest.cs | 12 +- .../Beatmaps/basic-expected-conversion.json | 921 ++++++++++++------ ...ock-repeat-slider-expected-conversion.json | 108 +- .../hardrock-spinner-expected-conversion.json | 51 +- .../hardrock-stream-expected-conversion.json | 99 +- ...t-bound-hr-offset-expected-conversion.json | 6 +- .../Beatmaps/slider-expected-conversion.json | 72 +- ...inner-and-circles-expected-conversion.json | 30 +- .../Beatmaps/spinner-expected-conversion.json | 51 +- 9 files changed, 926 insertions(+), 424 deletions(-) diff --git a/osu.Game.Rulesets.Catch.Tests/CatchBeatmapConversionTest.cs b/osu.Game.Rulesets.Catch.Tests/CatchBeatmapConversionTest.cs index 33fdcdaf1e..3352982c20 100644 --- a/osu.Game.Rulesets.Catch.Tests/CatchBeatmapConversionTest.cs +++ b/osu.Game.Rulesets.Catch.Tests/CatchBeatmapConversionTest.cs @@ -70,6 +70,7 @@ namespace osu.Game.Rulesets.Catch.Tests HitObject = hitObject; startTime = 0; position = 0; + hyperDash = false; } private double startTime; @@ -88,8 +89,17 @@ namespace osu.Game.Rulesets.Catch.Tests set => position = value; } + private bool hyperDash; + + public bool HyperDash + { + get => (HitObject as PalpableCatchHitObject)?.HyperDash ?? hyperDash; + set => hyperDash = value; + } + public bool Equals(ConvertValue other) => Precision.AlmostEquals(StartTime, other.StartTime, conversion_lenience) - && Precision.AlmostEquals(Position, other.Position, conversion_lenience); + && Precision.AlmostEquals(Position, other.Position, conversion_lenience) + && HyperDash == other.hyperDash; } } diff --git a/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/basic-expected-conversion.json b/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/basic-expected-conversion.json index b65d54a565..07ceb199bd 100644 --- a/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/basic-expected-conversion.json +++ b/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/basic-expected-conversion.json @@ -3,135 +3,168 @@ "StartTime": 500, "Objects": [{ "StartTime": 500, - "Position": 96 + "Position": 96, + "HyperDash": false }, { "StartTime": 562, - "Position": 100.84 + "Position": 100.84, + "HyperDash": false }, { "StartTime": 625, - "Position": 125 + "Position": 125, + "HyperDash": false }, { "StartTime": 687, - "Position": 152.84 + "Position": 152.84, + "HyperDash": false }, { "StartTime": 750, - "Position": 191 + "Position": 191, + "HyperDash": false }, { "StartTime": 812, - "Position": 212.84 + "Position": 212.84, + "HyperDash": false }, { "StartTime": 875, - "Position": 217 + "Position": 217, + "HyperDash": false }, { "StartTime": 937, - "Position": 234.84 + "Position": 234.84, + "HyperDash": false }, { "StartTime": 1000, - "Position": 256 + "Position": 256, + "HyperDash": false }, { "StartTime": 1062, - "Position": 267.84 + "Position": 267.84, + "HyperDash": false }, { "StartTime": 1125, - "Position": 284 + "Position": 284, + "HyperDash": false }, { "StartTime": 1187, - "Position": 311.84 + "Position": 311.84, + "HyperDash": false }, { "StartTime": 1250, - "Position": 350 + "Position": 350, + "HyperDash": false }, { "StartTime": 1312, - "Position": 359.84 + "Position": 359.84, + "HyperDash": false }, { "StartTime": 1375, - "Position": 367 + "Position": 367, + "HyperDash": false }, { "StartTime": 1437, - "Position": 400.84 + "Position": 400.84, + "HyperDash": false }, { "StartTime": 1500, - "Position": 416 + "Position": 416, + "HyperDash": false }, { "StartTime": 1562, - "Position": 377.159973 + "Position": 377.159973, + "HyperDash": false }, { "StartTime": 1625, - "Position": 367 + "Position": 367, + "HyperDash": false }, { "StartTime": 1687, - "Position": 374.159973 + "Position": 374.159973, + "HyperDash": false }, { "StartTime": 1750, - "Position": 353 + "Position": 353, + "HyperDash": false }, { "StartTime": 1812, - "Position": 329.159973 + "Position": 329.159973, + "HyperDash": false }, { "StartTime": 1875, - "Position": 288 + "Position": 288, + "HyperDash": false }, { "StartTime": 1937, - "Position": 259.159973 + "Position": 259.159973, + "HyperDash": false }, { "StartTime": 2000, - "Position": 256 + "Position": 256, + "HyperDash": false }, { "StartTime": 2058, - "Position": 232.44 + "Position": 232.44, + "HyperDash": false }, { "StartTime": 2116, - "Position": 222.879974 + "Position": 222.879974, + "HyperDash": false }, { "StartTime": 2174, - "Position": 185.319992 + "Position": 185.319992, + "HyperDash": false }, { "StartTime": 2232, - "Position": 177.76001 + "Position": 177.76001, + "HyperDash": false }, { "StartTime": 2290, - "Position": 162.200012 + "Position": 162.200012, + "HyperDash": false }, { "StartTime": 2348, - "Position": 158.639984 + "Position": 158.639984, + "HyperDash": false }, { "StartTime": 2406, - "Position": 111.079994 + "Position": 111.079994, + "HyperDash": false }, { "StartTime": 2500, - "Position": 96 + "Position": 96, + "HyperDash": false } ] }, @@ -139,71 +172,88 @@ "StartTime": 3000, "Objects": [{ "StartTime": 3000, - "Position": 18 + "Position": 18, + "HyperDash": false }, { "StartTime": 3062, - "Position": 249 + "Position": 249, + "HyperDash": false }, { "StartTime": 3125, - "Position": 184 + "Position": 184, + "HyperDash": false }, { "StartTime": 3187, - "Position": 477 + "Position": 477, + "HyperDash": false }, { "StartTime": 3250, - "Position": 43 + "Position": 43, + "HyperDash": false }, { "StartTime": 3312, - "Position": 494 + "Position": 494, + "HyperDash": false }, { "StartTime": 3375, - "Position": 135 + "Position": 135, + "HyperDash": false }, { "StartTime": 3437, - "Position": 30 + "Position": 30, + "HyperDash": false }, { "StartTime": 3500, - "Position": 11 + "Position": 11, + "HyperDash": false }, { "StartTime": 3562, - "Position": 239 + "Position": 239, + "HyperDash": false }, { "StartTime": 3625, - "Position": 505 + "Position": 505, + "HyperDash": false }, { "StartTime": 3687, - "Position": 353 + "Position": 353, + "HyperDash": false }, { "StartTime": 3750, - "Position": 136 + "Position": 136, + "HyperDash": false }, { "StartTime": 3812, - "Position": 135 + "Position": 135, + "HyperDash": false }, { "StartTime": 3875, - "Position": 346 + "Position": 346, + "HyperDash": false }, { "StartTime": 3937, - "Position": 39 + "Position": 39, + "HyperDash": false }, { "StartTime": 4000, - "Position": 300 + "Position": 300, + "HyperDash": false } ] }, @@ -211,71 +261,88 @@ "StartTime": 4500, "Objects": [{ "StartTime": 4500, - "Position": 398 + "Position": 398, + "HyperDash": false }, { "StartTime": 4562, - "Position": 151 + "Position": 151, + "HyperDash": false }, { "StartTime": 4625, - "Position": 73 + "Position": 73, + "HyperDash": false }, { "StartTime": 4687, - "Position": 311 + "Position": 311, + "HyperDash": false }, { "StartTime": 4750, - "Position": 90 + "Position": 90, + "HyperDash": false }, { "StartTime": 4812, - "Position": 264 + "Position": 264, + "HyperDash": false }, { "StartTime": 4875, - "Position": 477 + "Position": 477, + "HyperDash": false }, { "StartTime": 4937, - "Position": 473 + "Position": 473, + "HyperDash": false }, { "StartTime": 5000, - "Position": 120 + "Position": 120, + "HyperDash": false }, { "StartTime": 5062, - "Position": 115 + "Position": 115, + "HyperDash": false }, { "StartTime": 5125, - "Position": 163 + "Position": 163, + "HyperDash": false }, { "StartTime": 5187, - "Position": 447 + "Position": 447, + "HyperDash": false }, { "StartTime": 5250, - "Position": 72 + "Position": 72, + "HyperDash": false }, { "StartTime": 5312, - "Position": 257 + "Position": 257, + "HyperDash": false }, { "StartTime": 5375, - "Position": 153 + "Position": 153, + "HyperDash": false }, { "StartTime": 5437, - "Position": 388 + "Position": 388, + "HyperDash": false }, { "StartTime": 5500, - "Position": 336 + "Position": 336, + "HyperDash": false } ] }, @@ -283,39 +350,48 @@ "StartTime": 6000, "Objects": [{ "StartTime": 6000, - "Position": 13 + "Position": 13, + "HyperDash": false }, { "StartTime": 6062, - "Position": 429 + "Position": 429, + "HyperDash": false }, { "StartTime": 6125, - "Position": 381 + "Position": 381, + "HyperDash": false }, { "StartTime": 6187, - "Position": 186 + "Position": 186, + "HyperDash": false }, { "StartTime": 6250, - "Position": 267 + "Position": 267, + "HyperDash": false }, { "StartTime": 6312, - "Position": 305 + "Position": 305, + "HyperDash": false }, { "StartTime": 6375, - "Position": 456 + "Position": 456, + "HyperDash": false }, { "StartTime": 6437, - "Position": 26 + "Position": 26, + "HyperDash": false }, { "StartTime": 6500, - "Position": 238 + "Position": 238, + "HyperDash": false } ] }, @@ -323,71 +399,88 @@ "StartTime": 7000, "Objects": [{ "StartTime": 7000, - "Position": 256 + "Position": 256, + "HyperDash": false }, { "StartTime": 7062, - "Position": 262.84 + "Position": 262.84, + "HyperDash": false }, { "StartTime": 7125, - "Position": 295 + "Position": 295, + "HyperDash": false }, { "StartTime": 7187, - "Position": 303.84 + "Position": 303.84, + "HyperDash": false }, { "StartTime": 7250, - "Position": 336 + "Position": 336, + "HyperDash": false }, { "StartTime": 7312, - "Position": 319.16 + "Position": 319.16, + "HyperDash": false }, { "StartTime": 7375, - "Position": 306 + "Position": 306, + "HyperDash": false }, { "StartTime": 7437, - "Position": 272.16 + "Position": 272.16, + "HyperDash": false }, { "StartTime": 7500, - "Position": 256 + "Position": 256, + "HyperDash": false }, { "StartTime": 7562, - "Position": 255.84 + "Position": 255.84, + "HyperDash": false }, { "StartTime": 7625, - "Position": 300 + "Position": 300, + "HyperDash": false }, { "StartTime": 7687, - "Position": 320.84 + "Position": 320.84, + "HyperDash": false }, { "StartTime": 7750, - "Position": 336 + "Position": 336, + "HyperDash": false }, { "StartTime": 7803, - "Position": 319.04 + "Position": 319.04, + "HyperDash": false }, { "StartTime": 7857, - "Position": 283.76 + "Position": 283.76, + "HyperDash": false }, { "StartTime": 7910, - "Position": 265.8 + "Position": 265.8, + "HyperDash": false }, { "StartTime": 8000, - "Position": 256 + "Position": 256, + "HyperDash": false } ] }, @@ -395,167 +488,208 @@ "StartTime": 8500, "Objects": [{ "StartTime": 8500, - "Position": 32 + "Position": 32, + "HyperDash": false }, { "StartTime": 8562, - "Position": 21.8515015 + "Position": 21.8515015, + "HyperDash": false }, { "StartTime": 8625, - "Position": 44.5659637 + "Position": 44.5659637, + "HyperDash": false }, { "StartTime": 8687, - "Position": 33.3433228 + "Position": 33.3433228, + "HyperDash": false }, { "StartTime": 8750, - "Position": 63.58974 + "Position": 63.58974, + "HyperDash": false }, { "StartTime": 8812, - "Position": 71.23422 + "Position": 71.23422, + "HyperDash": false }, { "StartTime": 8875, - "Position": 62.7117844 + "Position": 62.7117844, + "HyperDash": false }, { "StartTime": 8937, - "Position": 65.52607 + "Position": 65.52607, + "HyperDash": false }, { "StartTime": 9000, - "Position": 101.81015 + "Position": 101.81015, + "HyperDash": false }, { "StartTime": 9062, - "Position": 134.47818 + "Position": 134.47818, + "HyperDash": false }, { "StartTime": 9125, - "Position": 141.414444 + "Position": 141.414444, + "HyperDash": false }, { "StartTime": 9187, - "Position": 164.1861 + "Position": 164.1861, + "HyperDash": false }, { "StartTime": 9250, - "Position": 176.600418 + "Position": 176.600418, + "HyperDash": false }, { "StartTime": 9312, - "Position": 184.293015 + "Position": 184.293015, + "HyperDash": false }, { "StartTime": 9375, - "Position": 212.2076 + "Position": 212.2076, + "HyperDash": false }, { "StartTime": 9437, - "Position": 236.438324 + "Position": 236.438324, + "HyperDash": false }, { "StartTime": 9500, - "Position": 237.2304 + "Position": 237.2304, + "HyperDash": false }, { "StartTime": 9562, - "Position": 241.253983 + "Position": 241.253983, + "HyperDash": false }, { "StartTime": 9625, - "Position": 233.950623 + "Position": 233.950623, + "HyperDash": false }, { "StartTime": 9687, - "Position": 265.3786 + "Position": 265.3786, + "HyperDash": false }, { "StartTime": 9750, - "Position": 236.8865 + "Position": 236.8865, + "HyperDash": false }, { "StartTime": 9812, - "Position": 273.38974 + "Position": 273.38974, + "HyperDash": false }, { "StartTime": 9875, - "Position": 267.701874 + "Position": 267.701874, + "HyperDash": false }, { "StartTime": 9937, - "Position": 263.2331 + "Position": 263.2331, + "HyperDash": false }, { "StartTime": 10000, - "Position": 270.339874 + "Position": 270.339874, + "HyperDash": false }, { "StartTime": 10062, - "Position": 291.9349 + "Position": 291.9349, + "HyperDash": false }, { "StartTime": 10125, - "Position": 294.2969 + "Position": 294.2969, + "HyperDash": false }, { "StartTime": 10187, - "Position": 307.834137 + "Position": 307.834137, + "HyperDash": false }, { "StartTime": 10250, - "Position": 310.6449 + "Position": 310.6449, + "HyperDash": false }, { "StartTime": 10312, - "Position": 344.746338 + "Position": 344.746338, + "HyperDash": false }, { "StartTime": 10375, - "Position": 349.21875 + "Position": 349.21875, + "HyperDash": false }, { "StartTime": 10437, - "Position": 373.943 + "Position": 373.943, + "HyperDash": false }, { "StartTime": 10500, - "Position": 401.0588 + "Position": 401.0588, + "HyperDash": false }, { "StartTime": 10558, - "Position": 421.21347 + "Position": 421.21347, + "HyperDash": false }, { "StartTime": 10616, - "Position": 431.6034 + "Position": 431.6034, + "HyperDash": false }, { "StartTime": 10674, - "Position": 433.835754 + "Position": 433.835754, + "HyperDash": false }, { "StartTime": 10732, - "Position": 452.5042 + "Position": 452.5042, + "HyperDash": false }, { "StartTime": 10790, - "Position": 486.290955 + "Position": 486.290955, + "HyperDash": false }, { "StartTime": 10848, - "Position": 488.943237 + "Position": 488.943237, + "HyperDash": false }, { "StartTime": 10906, - "Position": 493.3372 + "Position": 493.3372, + "HyperDash": false }, { "StartTime": 10999, - "Position": 508.166229 + "Position": 508.166229, + "HyperDash": false } ] }, @@ -563,39 +697,48 @@ "StartTime": 11500, "Objects": [{ "StartTime": 11500, - "Position": 97 + "Position": 97, + "HyperDash": false }, { "StartTime": 11562, - "Position": 267 + "Position": 267, + "HyperDash": false }, { "StartTime": 11625, - "Position": 116 + "Position": 116, + "HyperDash": false }, { "StartTime": 11687, - "Position": 451 + "Position": 451, + "HyperDash": false }, { "StartTime": 11750, - "Position": 414 + "Position": 414, + "HyperDash": false }, { "StartTime": 11812, - "Position": 88 + "Position": 88, + "HyperDash": false }, { "StartTime": 11875, - "Position": 257 + "Position": 257, + "HyperDash": false }, { "StartTime": 11937, - "Position": 175 + "Position": 175, + "HyperDash": false }, { "StartTime": 12000, - "Position": 38 + "Position": 38, + "HyperDash": false } ] }, @@ -603,263 +746,328 @@ "StartTime": 12500, "Objects": [{ "StartTime": 12500, - "Position": 512 + "Position": 512, + "HyperDash": false }, { "StartTime": 12562, - "Position": 494.3132 + "Position": 494.3132, + "HyperDash": false }, { "StartTime": 12625, - "Position": 461.3089 + "Position": 461.3089, + "HyperDash": false }, { "StartTime": 12687, - "Position": 469.6221 + "Position": 469.6221, + "HyperDash": false }, { "StartTime": 12750, - "Position": 441.617767 + "Position": 441.617767, + "HyperDash": false }, { "StartTime": 12812, - "Position": 402.930969 + "Position": 402.930969, + "HyperDash": false }, { "StartTime": 12875, - "Position": 407.926666 + "Position": 407.926666, + "HyperDash": false }, { "StartTime": 12937, - "Position": 364.239868 + "Position": 364.239868, + "HyperDash": false }, { "StartTime": 13000, - "Position": 353.235535 + "Position": 353.235535, + "HyperDash": false }, { "StartTime": 13062, - "Position": 320.548767 + "Position": 320.548767, + "HyperDash": false }, { "StartTime": 13125, - "Position": 303.544434 + "Position": 303.544434, + "HyperDash": false }, { "StartTime": 13187, - "Position": 295.857635 + "Position": 295.857635, + "HyperDash": false }, { "StartTime": 13250, - "Position": 265.853333 + "Position": 265.853333, + "HyperDash": false }, { "StartTime": 13312, - "Position": 272.166534 + "Position": 272.166534, + "HyperDash": false }, { "StartTime": 13375, - "Position": 240.1622 + "Position": 240.1622, + "HyperDash": false }, { "StartTime": 13437, - "Position": 229.4754 + "Position": 229.4754, + "HyperDash": false }, { "StartTime": 13500, - "Position": 194.471069 + "Position": 194.471069, + "HyperDash": false }, { "StartTime": 13562, - "Position": 158.784271 + "Position": 158.784271, + "HyperDash": false }, { "StartTime": 13625, - "Position": 137.779968 + "Position": 137.779968, + "HyperDash": false }, { "StartTime": 13687, - "Position": 147.09314 + "Position": 147.09314, + "HyperDash": false }, { "StartTime": 13750, - "Position": 122.088837 + "Position": 122.088837, + "HyperDash": false }, { "StartTime": 13812, - "Position": 77.40204 + "Position": 77.40204, + "HyperDash": false }, { "StartTime": 13875, - "Position": 79.3977356 + "Position": 79.3977356, + "HyperDash": false }, { "StartTime": 13937, - "Position": 56.710907 + "Position": 56.710907, + "HyperDash": false }, { "StartTime": 14000, - "Position": 35.7066345 + "Position": 35.7066345, + "HyperDash": false }, { "StartTime": 14062, - "Position": 1.01980591 + "Position": 1.01980591, + "HyperDash": false }, { "StartTime": 14125, - "Position": 0 + "Position": 0, + "HyperDash": false }, { "StartTime": 14187, - "Position": 21.7696266 + "Position": 21.7696266, + "HyperDash": false }, { "StartTime": 14250, - "Position": 49.0119171 + "Position": 49.0119171, + "HyperDash": false }, { "StartTime": 14312, - "Position": 48.9488258 + "Position": 48.9488258, + "HyperDash": false }, { "StartTime": 14375, - "Position": 87.19112 + "Position": 87.19112, + "HyperDash": false }, { "StartTime": 14437, - "Position": 97.12803 + "Position": 97.12803, + "HyperDash": false }, { "StartTime": 14500, - "Position": 118.370323 + "Position": 118.370323, + "HyperDash": false }, { "StartTime": 14562, - "Position": 130.307236 + "Position": 130.307236, + "HyperDash": false }, { "StartTime": 14625, - "Position": 154.549515 + "Position": 154.549515, + "HyperDash": false }, { "StartTime": 14687, - "Position": 190.486435 + "Position": 190.486435, + "HyperDash": false }, { "StartTime": 14750, - "Position": 211.728714 + "Position": 211.728714, + "HyperDash": false }, { "StartTime": 14812, - "Position": 197.665634 + "Position": 197.665634, + "HyperDash": false }, { "StartTime": 14875, - "Position": 214.907928 + "Position": 214.907928, + "HyperDash": false }, { "StartTime": 14937, - "Position": 263.844849 + "Position": 263.844849, + "HyperDash": false }, { "StartTime": 15000, - "Position": 271.087128 + "Position": 271.087128, + "HyperDash": false }, { "StartTime": 15062, - "Position": 270.024017 + "Position": 270.024017, + "HyperDash": false }, { "StartTime": 15125, - "Position": 308.266327 + "Position": 308.266327, + "HyperDash": false }, { "StartTime": 15187, - "Position": 313.203247 + "Position": 313.203247, + "HyperDash": false }, { "StartTime": 15250, - "Position": 328.445526 + "Position": 328.445526, + "HyperDash": false }, { "StartTime": 15312, - "Position": 370.382446 + "Position": 370.382446, + "HyperDash": false }, { "StartTime": 15375, - "Position": 387.624725 + "Position": 387.624725, + "HyperDash": false }, { "StartTime": 15437, - "Position": 421.561646 + "Position": 421.561646, + "HyperDash": false }, { "StartTime": 15500, - "Position": 423.803925 + "Position": 423.803925, + "HyperDash": false }, { "StartTime": 15562, - "Position": 444.740845 + "Position": 444.740845, + "HyperDash": false }, { "StartTime": 15625, - "Position": 469.983124 + "Position": 469.983124, + "HyperDash": false }, { "StartTime": 15687, - "Position": 473.920044 + "Position": 473.920044, + "HyperDash": false }, { "StartTime": 15750, - "Position": 501.162323 + "Position": 501.162323, + "HyperDash": false }, { "StartTime": 15812, - "Position": 488.784332 + "Position": 488.784332, + "HyperDash": false }, { "StartTime": 15875, - "Position": 466.226227 + "Position": 466.226227, + "HyperDash": false }, { "StartTime": 15937, - "Position": 445.978638 + "Position": 445.978638, + "HyperDash": false }, { "StartTime": 16000, - "Position": 446.420532 + "Position": 446.420532, + "HyperDash": false }, { "StartTime": 16058, - "Position": 428.4146 + "Position": 428.4146, + "HyperDash": false }, { "StartTime": 16116, - "Position": 420.408844 + "Position": 420.408844, + "HyperDash": false }, { "StartTime": 16174, - "Position": 374.402924 + "Position": 374.402924, + "HyperDash": false }, { "StartTime": 16232, - "Position": 371.397156 + "Position": 371.397156, + "HyperDash": false }, { "StartTime": 16290, - "Position": 350.391235 + "Position": 350.391235, + "HyperDash": false }, { "StartTime": 16348, - "Position": 340.385468 + "Position": 340.385468, + "HyperDash": false }, { "StartTime": 16406, - "Position": 337.3797 + "Position": 337.3797, + "HyperDash": false }, { "StartTime": 16500, - "Position": 291.1977 + "Position": 291.1977, + "HyperDash": false } ] }, @@ -867,71 +1075,88 @@ "StartTime": 17000, "Objects": [{ "StartTime": 17000, - "Position": 256 + "Position": 256, + "HyperDash": false }, { "StartTime": 17062, - "Position": 247.16 + "Position": 247.16, + "HyperDash": false }, { "StartTime": 17125, - "Position": 211 + "Position": 211, + "HyperDash": false }, { "StartTime": 17187, - "Position": 183.16 + "Position": 183.16, + "HyperDash": false }, { "StartTime": 17250, - "Position": 176 + "Position": 176, + "HyperDash": false }, { "StartTime": 17312, - "Position": 204.84 + "Position": 204.84, + "HyperDash": false }, { "StartTime": 17375, - "Position": 218 + "Position": 218, + "HyperDash": false }, { "StartTime": 17437, - "Position": 231.84 + "Position": 231.84, + "HyperDash": false }, { "StartTime": 17500, - "Position": 256 + "Position": 256, + "HyperDash": false }, { "StartTime": 17562, - "Position": 229.16 + "Position": 229.16, + "HyperDash": false }, { "StartTime": 17625, - "Position": 227 + "Position": 227, + "HyperDash": false }, { "StartTime": 17687, - "Position": 186.16 + "Position": 186.16, + "HyperDash": false }, { "StartTime": 17750, - "Position": 176 + "Position": 176, + "HyperDash": false }, { "StartTime": 17803, - "Position": 211.959991 + "Position": 211.959991, + "HyperDash": false }, { "StartTime": 17857, - "Position": 197.23999 + "Position": 197.23999, + "HyperDash": false }, { "StartTime": 17910, - "Position": 225.200012 + "Position": 225.200012, + "HyperDash": false }, { "StartTime": 18000, - "Position": 256 + "Position": 256, + "HyperDash": false } ] }, @@ -939,71 +1164,88 @@ "StartTime": 18500, "Objects": [{ "StartTime": 18500, - "Position": 437 + "Position": 437, + "HyperDash": false }, { "StartTime": 18559, - "Position": 289 + "Position": 289, + "HyperDash": false }, { "StartTime": 18618, - "Position": 464 + "Position": 464, + "HyperDash": false }, { "StartTime": 18678, - "Position": 36 + "Position": 36, + "HyperDash": false }, { "StartTime": 18737, - "Position": 378 + "Position": 378, + "HyperDash": false }, { "StartTime": 18796, - "Position": 297 + "Position": 297, + "HyperDash": false }, { "StartTime": 18856, - "Position": 418 + "Position": 418, + "HyperDash": false }, { "StartTime": 18915, - "Position": 329 + "Position": 329, + "HyperDash": false }, { "StartTime": 18975, - "Position": 338 + "Position": 338, + "HyperDash": false }, { "StartTime": 19034, - "Position": 394 + "Position": 394, + "HyperDash": false }, { "StartTime": 19093, - "Position": 40 + "Position": 40, + "HyperDash": false }, { "StartTime": 19153, - "Position": 13 + "Position": 13, + "HyperDash": false }, { "StartTime": 19212, - "Position": 80 + "Position": 80, + "HyperDash": false }, { "StartTime": 19271, - "Position": 138 + "Position": 138, + "HyperDash": false }, { "StartTime": 19331, - "Position": 311 + "Position": 311, + "HyperDash": false }, { "StartTime": 19390, - "Position": 216 + "Position": 216, + "HyperDash": false }, { "StartTime": 19450, - "Position": 310 + "Position": 310, + "HyperDash": false } ] }, @@ -1011,263 +1253,328 @@ "StartTime": 19875, "Objects": [{ "StartTime": 19875, - "Position": 216 + "Position": 216, + "HyperDash": false }, { "StartTime": 19937, - "Position": 228.307053 + "Position": 228.307053, + "HyperDash": false }, { "StartTime": 20000, - "Position": 214.036865 + "Position": 214.036865, + "HyperDash": false }, { "StartTime": 20062, - "Position": 224.312088 + "Position": 224.312088, + "HyperDash": false }, { "StartTime": 20125, - "Position": 253.838928 + "Position": 253.838928, + "HyperDash": false }, { "StartTime": 20187, - "Position": 259.9743 + "Position": 259.9743, + "HyperDash": false }, { "StartTime": 20250, - "Position": 299.999146 + "Position": 299.999146, + "HyperDash": false }, { "StartTime": 20312, - "Position": 289.669067 + "Position": 289.669067, + "HyperDash": false }, { "StartTime": 20375, - "Position": 317.446747 + "Position": 317.446747, + "HyperDash": false }, { "StartTime": 20437, - "Position": 344.750275 + "Position": 344.750275, + "HyperDash": false }, { "StartTime": 20500, - "Position": 328.0156 + "Position": 328.0156, + "HyperDash": false }, { "StartTime": 20562, - "Position": 331.472168 + "Position": 331.472168, + "HyperDash": false }, { "StartTime": 20625, - "Position": 302.165466 + "Position": 302.165466, + "HyperDash": false }, { "StartTime": 20687, - "Position": 303.044617 + "Position": 303.044617, + "HyperDash": false }, { "StartTime": 20750, - "Position": 306.457367 + "Position": 306.457367, + "HyperDash": false }, { "StartTime": 20812, - "Position": 265.220581 + "Position": 265.220581, + "HyperDash": false }, { "StartTime": 20875, - "Position": 270.3294 + "Position": 270.3294, + "HyperDash": false }, { "StartTime": 20937, - "Position": 257.57605 + "Position": 257.57605, + "HyperDash": false }, { "StartTime": 21000, - "Position": 247.803329 + "Position": 247.803329, + "HyperDash": false }, { "StartTime": 21062, - "Position": 225.958359 + "Position": 225.958359, + "HyperDash": false }, { "StartTime": 21125, - "Position": 201.79332 + "Position": 201.79332, + "HyperDash": false }, { "StartTime": 21187, - "Position": 170.948349 + "Position": 170.948349, + "HyperDash": false }, { "StartTime": 21250, - "Position": 146.78334 + "Position": 146.78334, + "HyperDash": false }, { "StartTime": 21312, - "Position": 149.93837 + "Position": 149.93837, + "HyperDash": false }, { "StartTime": 21375, - "Position": 119.121056 + "Position": 119.121056, + "HyperDash": false }, { "StartTime": 21437, - "Position": 133.387573 + "Position": 133.387573, + "HyperDash": false }, { "StartTime": 21500, - "Position": 117.503014 + "Position": 117.503014, + "HyperDash": false }, { "StartTime": 21562, - "Position": 103.749374 + "Position": 103.749374, + "HyperDash": false }, { "StartTime": 21625, - "Position": 127.165535 + "Position": 127.165535, + "HyperDash": false }, { "StartTime": 21687, - "Position": 113.029991 + "Position": 113.029991, + "HyperDash": false }, { "StartTime": 21750, - "Position": 101.547928 + "Position": 101.547928, + "HyperDash": false }, { "StartTime": 21812, - "Position": 133.856232 + "Position": 133.856232, + "HyperDash": false }, { "StartTime": 21875, - "Position": 124.28746 + "Position": 124.28746, + "HyperDash": false }, { "StartTime": 21937, - "Position": 121.754929 + "Position": 121.754929, + "HyperDash": false }, { "StartTime": 22000, - "Position": 155.528732 + "Position": 155.528732, + "HyperDash": false }, { "StartTime": 22062, - "Position": 142.1691 + "Position": 142.1691, + "HyperDash": false }, { "StartTime": 22125, - "Position": 186.802155 + "Position": 186.802155, + "HyperDash": false }, { "StartTime": 22187, - "Position": 198.6452 + "Position": 198.6452, + "HyperDash": false }, { "StartTime": 22250, - "Position": 191.892181 + "Position": 191.892181, + "HyperDash": false }, { "StartTime": 22312, - "Position": 232.713028 + "Position": 232.713028, + "HyperDash": false }, { "StartTime": 22375, - "Position": 240.4715 + "Position": 240.4715, + "HyperDash": false }, { "StartTime": 22437, - "Position": 278.3719 + "Position": 278.3719, + "HyperDash": false }, { "StartTime": 22500, - "Position": 288.907257 + "Position": 288.907257, + "HyperDash": false }, { "StartTime": 22562, - "Position": 297.353119 + "Position": 297.353119, + "HyperDash": false }, { "StartTime": 22625, - "Position": 301.273376 + "Position": 301.273376, + "HyperDash": false }, { "StartTime": 22687, - "Position": 339.98288 + "Position": 339.98288, + "HyperDash": false }, { "StartTime": 22750, - "Position": 353.078552 + "Position": 353.078552, + "HyperDash": false }, { "StartTime": 22812, - "Position": 363.8958 + "Position": 363.8958, + "HyperDash": false }, { "StartTime": 22875, - "Position": 398.054047 + "Position": 398.054047, + "HyperDash": false }, { "StartTime": 22937, - "Position": 419.739441 + "Position": 419.739441, + "HyperDash": false }, { "StartTime": 23000, - "Position": 435.178467 + "Position": 435.178467, + "HyperDash": false }, { "StartTime": 23062, - "Position": 420.8687 + "Position": 420.8687, + "HyperDash": false }, { "StartTime": 23125, - "Position": 448.069977 + "Position": 448.069977, + "HyperDash": false }, { "StartTime": 23187, - "Position": 425.688477 + "Position": 425.688477, + "HyperDash": false }, { "StartTime": 23250, - "Position": 426.9612 + "Position": 426.9612, + "HyperDash": false }, { "StartTime": 23312, - "Position": 454.92807 + "Position": 454.92807, + "HyperDash": false }, { "StartTime": 23375, - "Position": 439.749878 + "Position": 439.749878, + "HyperDash": false }, { "StartTime": 23433, - "Position": 440.644684 + "Position": 440.644684, + "HyperDash": false }, { "StartTime": 23491, - "Position": 445.7359 + "Position": 445.7359, + "HyperDash": false }, { "StartTime": 23549, - "Position": 432.0944 + "Position": 432.0944, + "HyperDash": false }, { "StartTime": 23607, - "Position": 415.796173 + "Position": 415.796173, + "HyperDash": false }, { "StartTime": 23665, - "Position": 407.897461 + "Position": 407.897461, + "HyperDash": false }, { "StartTime": 23723, - "Position": 409.462555 + "Position": 409.462555, + "HyperDash": false }, { "StartTime": 23781, - "Position": 406.53775 + "Position": 406.53775, + "HyperDash": false }, { "StartTime": 23874, - "Position": 408.720825 + "Position": 408.720825, + "HyperDash": false } ] } diff --git a/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/hardrock-repeat-slider-expected-conversion.json b/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/hardrock-repeat-slider-expected-conversion.json index 83f9e30800..081b574c5b 100644 --- a/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/hardrock-repeat-slider-expected-conversion.json +++ b/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/hardrock-repeat-slider-expected-conversion.json @@ -3,147 +3,183 @@ "StartTime": 369, "Objects": [{ "StartTime": 369, - "Position": 177 + "Position": 177, + "HyperDash": false }, { "StartTime": 450, - "Position": 216.539276 + "Position": 216.539276, + "HyperDash": false }, { "StartTime": 532, - "Position": 256.5667 + "Position": 256.5667, + "HyperDash": false }, { "StartTime": 614, - "Position": 296.594116 + "Position": 296.594116, + "HyperDash": false }, { "StartTime": 696, - "Position": 336.621521 + "Position": 336.621521, + "HyperDash": false }, { "StartTime": 778, - "Position": 376.99762 + "Position": 376.99762, + "HyperDash": false }, { "StartTime": 860, - "Position": 337.318878 + "Position": 337.318878, + "HyperDash": false }, { "StartTime": 942, - "Position": 297.291443 + "Position": 297.291443, + "HyperDash": false }, { "StartTime": 1024, - "Position": 257.264038 + "Position": 257.264038, + "HyperDash": false }, { "StartTime": 1106, - "Position": 217.2366 + "Position": 217.2366, + "HyperDash": false }, { "StartTime": 1188, - "Position": 177 + "Position": 177, + "HyperDash": false }, { "StartTime": 1270, - "Position": 216.818192 + "Position": 216.818192, + "HyperDash": false }, { "StartTime": 1352, - "Position": 256.8456 + "Position": 256.8456, + "HyperDash": false }, { "StartTime": 1434, - "Position": 296.873047 + "Position": 296.873047, + "HyperDash": false }, { "StartTime": 1516, - "Position": 336.900452 + "Position": 336.900452, + "HyperDash": false }, { "StartTime": 1598, - "Position": 376.99762 + "Position": 376.99762, + "HyperDash": false }, { "StartTime": 1680, - "Position": 337.039948 + "Position": 337.039948, + "HyperDash": false }, { "StartTime": 1762, - "Position": 297.0125 + "Position": 297.0125, + "HyperDash": false }, { "StartTime": 1844, - "Position": 256.9851 + "Position": 256.9851, + "HyperDash": false }, { "StartTime": 1926, - "Position": 216.957672 + "Position": 216.957672, + "HyperDash": false }, { "StartTime": 2008, - "Position": 177 + "Position": 177, + "HyperDash": false }, { "StartTime": 2090, - "Position": 217.097137 + "Position": 217.097137, + "HyperDash": false }, { "StartTime": 2172, - "Position": 257.124573 + "Position": 257.124573, + "HyperDash": false }, { "StartTime": 2254, - "Position": 297.152 + "Position": 297.152, + "HyperDash": false }, { "StartTime": 2336, - "Position": 337.179443 + "Position": 337.179443, + "HyperDash": false }, { "StartTime": 2418, - "Position": 376.99762 + "Position": 376.99762, + "HyperDash": false }, { "StartTime": 2500, - "Position": 336.760956 + "Position": 336.760956, + "HyperDash": false }, { "StartTime": 2582, - "Position": 296.733643 + "Position": 296.733643, + "HyperDash": false }, { "StartTime": 2664, - "Position": 256.7062 + "Position": 256.7062, + "HyperDash": false }, { "StartTime": 2746, - "Position": 216.678772 + "Position": 216.678772, + "HyperDash": false }, { "StartTime": 2828, - "Position": 177 + "Position": 177, + "HyperDash": false }, { "StartTime": 2909, - "Position": 216.887909 + "Position": 216.887909, + "HyperDash": false }, { "StartTime": 2991, - "Position": 256.915344 + "Position": 256.915344, + "HyperDash": false }, { "StartTime": 3073, - "Position": 296.942749 + "Position": 296.942749, + "HyperDash": false }, { "StartTime": 3155, - "Position": 336.970184 + "Position": 336.970184, + "HyperDash": false }, { "StartTime": 3237, - "Position": 376.99762 + "Position": 376.99762, + "HyperDash": false } ] }] diff --git a/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/hardrock-spinner-expected-conversion.json b/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/hardrock-spinner-expected-conversion.json index 7333b600fb..01f474c149 100644 --- a/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/hardrock-spinner-expected-conversion.json +++ b/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/hardrock-spinner-expected-conversion.json @@ -3,71 +3,88 @@ "StartTime": 369, "Objects": [{ "StartTime": 369, - "Position": 65 + "Position": 65, + "HyperDash": false }, { "StartTime": 450, - "Position": 482 + "Position": 482, + "HyperDash": false }, { "StartTime": 532, - "Position": 164 + "Position": 164, + "HyperDash": false }, { "StartTime": 614, - "Position": 315 + "Position": 315, + "HyperDash": false }, { "StartTime": 696, - "Position": 145 + "Position": 145, + "HyperDash": false }, { "StartTime": 778, - "Position": 159 + "Position": 159, + "HyperDash": false }, { "StartTime": 860, - "Position": 310 + "Position": 310, + "HyperDash": false }, { "StartTime": 942, - "Position": 441 + "Position": 441, + "HyperDash": false }, { "StartTime": 1024, - "Position": 428 + "Position": 428, + "HyperDash": false }, { "StartTime": 1106, - "Position": 243 + "Position": 243, + "HyperDash": false }, { "StartTime": 1188, - "Position": 422 + "Position": 422, + "HyperDash": false }, { "StartTime": 1270, - "Position": 481 + "Position": 481, + "HyperDash": false }, { "StartTime": 1352, - "Position": 104 + "Position": 104, + "HyperDash": false }, { "StartTime": 1434, - "Position": 473 + "Position": 473, + "HyperDash": false }, { "StartTime": 1516, - "Position": 135 + "Position": 135, + "HyperDash": false }, { "StartTime": 1598, - "Position": 360 + "Position": 360, + "HyperDash": false }, { "StartTime": 1680, - "Position": 123 + "Position": 123, + "HyperDash": false } ] }] diff --git a/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/hardrock-stream-expected-conversion.json b/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/hardrock-stream-expected-conversion.json index bbc16ab912..8eaaf3bb90 100644 --- a/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/hardrock-stream-expected-conversion.json +++ b/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/hardrock-stream-expected-conversion.json @@ -3,231 +3,264 @@ "StartTime": 369, "Objects": [{ "StartTime": 369, - "Position": 258 + "Position": 258, + "HyperDash": false }] }, { "StartTime": 450, "Objects": [{ "StartTime": 450, - "Position": 254 + "Position": 254, + "HyperDash": false }] }, { "StartTime": 532, "Objects": [{ "StartTime": 532, - "Position": 241 + "Position": 241, + "HyperDash": false }] }, { "StartTime": 614, "Objects": [{ "StartTime": 614, - "Position": 238 + "Position": 238, + "HyperDash": false }] }, { "StartTime": 696, "Objects": [{ "StartTime": 696, - "Position": 238 + "Position": 238, + "HyperDash": false }] }, { "StartTime": 778, "Objects": [{ "StartTime": 778, - "Position": 278 + "Position": 278, + "HyperDash": false }] }, { "StartTime": 860, "Objects": [{ "StartTime": 860, - "Position": 238 + "Position": 238, + "HyperDash": false }] }, { "StartTime": 942, "Objects": [{ "StartTime": 942, - "Position": 278 + "Position": 278, + "HyperDash": false }] }, { "StartTime": 1024, "Objects": [{ "StartTime": 1024, - "Position": 238 + "Position": 238, + "HyperDash": false }] }, { "StartTime": 1106, "Objects": [{ "StartTime": 1106, - "Position": 278 + "Position": 278, + "HyperDash": false }] }, { "StartTime": 1188, "Objects": [{ "StartTime": 1188, - "Position": 278 + "Position": 278, + "HyperDash": false }] }, { "StartTime": 1270, "Objects": [{ "StartTime": 1270, - "Position": 278 + "Position": 278, + "HyperDash": false }] }, { "StartTime": 1352, "Objects": [{ "StartTime": 1352, - "Position": 238 + "Position": 238, + "HyperDash": false }] }, { "StartTime": 1434, "Objects": [{ "StartTime": 1434, - "Position": 258 + "Position": 258, + "HyperDash": false }] }, { "StartTime": 1516, "Objects": [{ "StartTime": 1516, - "Position": 253 + "Position": 253, + "HyperDash": false }] }, { "StartTime": 1598, "Objects": [{ "StartTime": 1598, - "Position": 238 + "Position": 238, + "HyperDash": false }] }, { "StartTime": 1680, "Objects": [{ "StartTime": 1680, - "Position": 260 + "Position": 260, + "HyperDash": false }] }, { "StartTime": 1762, "Objects": [{ "StartTime": 1762, - "Position": 238 + "Position": 238, + "HyperDash": false }] }, { "StartTime": 1844, "Objects": [{ "StartTime": 1844, - "Position": 278 + "Position": 278, + "HyperDash": false }] }, { "StartTime": 1926, "Objects": [{ "StartTime": 1926, - "Position": 278 + "Position": 278, + "HyperDash": false }] }, { "StartTime": 2008, "Objects": [{ "StartTime": 2008, - "Position": 238 + "Position": 238, + "HyperDash": false }] }, { "StartTime": 2090, "Objects": [{ "StartTime": 2090, - "Position": 238 + "Position": 238, + "HyperDash": false }] }, { "StartTime": 2172, "Objects": [{ "StartTime": 2172, - "Position": 243 + "Position": 243, + "HyperDash": false }] }, { "StartTime": 2254, "Objects": [{ "StartTime": 2254, - "Position": 278 + "Position": 278, + "HyperDash": false }] }, { "StartTime": 2336, "Objects": [{ "StartTime": 2336, - "Position": 278 + "Position": 278, + "HyperDash": false }] }, { "StartTime": 2418, "Objects": [{ "StartTime": 2418, - "Position": 238 + "Position": 238, + "HyperDash": false }] }, { "StartTime": 2500, "Objects": [{ "StartTime": 2500, - "Position": 258 + "Position": 258, + "HyperDash": false }] }, { "StartTime": 2582, "Objects": [{ "StartTime": 2582, - "Position": 256 + "Position": 256, + "HyperDash": false }] }, { "StartTime": 2664, "Objects": [{ "StartTime": 2664, - "Position": 242 + "Position": 242, + "HyperDash": false }] }, { "StartTime": 2746, "Objects": [{ "StartTime": 2746, - "Position": 238 + "Position": 238, + "HyperDash": false }] }, { "StartTime": 2828, "Objects": [{ "StartTime": 2828, - "Position": 238 + "Position": 238, + "HyperDash": false }] }, { "StartTime": 2909, "Objects": [{ "StartTime": 2909, - "Position": 271 + "Position": 271, + "HyperDash": false }] }, { "StartTime": 2991, "Objects": [{ "StartTime": 2991, - "Position": 254 + "Position": 254, + "HyperDash": false }] } ] diff --git a/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/right-bound-hr-offset-expected-conversion.json b/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/right-bound-hr-offset-expected-conversion.json index 3bde97070c..5060389ad8 100644 --- a/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/right-bound-hr-offset-expected-conversion.json +++ b/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/right-bound-hr-offset-expected-conversion.json @@ -3,14 +3,16 @@ "StartTime": 3368, "Objects": [{ "StartTime": 3368, - "Position": 374 + "Position": 374, + "HyperDash": false }] }, { "StartTime": 3501, "Objects": [{ "StartTime": 3501, - "Position": 446 + "Position": 446, + "HyperDash": false }] } ] diff --git a/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/slider-expected-conversion.json b/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/slider-expected-conversion.json index 58c52b6867..2378ba5511 100644 --- a/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/slider-expected-conversion.json +++ b/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/slider-expected-conversion.json @@ -1 +1,71 @@ -{"Mappings":[{"StartTime":19184.0,"Objects":[{"StartTime":19184.0,"Position":320.0},{"StartTime":19263.0,"Position":311.730255},{"StartTime":19343.0,"Position":324.6205},{"StartTime":19423.0,"Position":343.0907},{"StartTime":19503.0,"Position":372.2917},{"StartTime":19582.0,"Position":385.194733},{"StartTime":19662.0,"Position":379.0426},{"StartTime":19742.0,"Position":385.1066},{"StartTime":19822.0,"Position":391.624664},{"StartTime":19919.0,"Position":386.27832},{"StartTime":20016.0,"Position":380.117035},{"StartTime":20113.0,"Position":381.664154},{"StartTime":20247.0,"Position":370.872864}]}]} \ No newline at end of file +{ + "Mappings": [{ + "StartTime": 19184, + "Objects": [{ + "StartTime": 19184, + "Position": 320, + "HyperDash": false + }, + { + "StartTime": 19263, + "Position": 311.730255, + "HyperDash": false + }, + { + "StartTime": 19343, + "Position": 324.6205, + "HyperDash": false + }, + { + "StartTime": 19423, + "Position": 343.0907, + "HyperDash": false + }, + { + "StartTime": 19503, + "Position": 372.2917, + "HyperDash": false + }, + { + "StartTime": 19582, + "Position": 385.194733, + "HyperDash": false + }, + { + "StartTime": 19662, + "Position": 379.0426, + "HyperDash": false + }, + { + "StartTime": 19742, + "Position": 385.1066, + "HyperDash": false + }, + { + "StartTime": 19822, + "Position": 391.624664, + "HyperDash": false + }, + { + "StartTime": 19919, + "Position": 386.27832, + "HyperDash": false + }, + { + "StartTime": 20016, + "Position": 380.117035, + "HyperDash": false + }, + { + "StartTime": 20113, + "Position": 381.664154, + "HyperDash": false + }, + { + "StartTime": 20247, + "Position": 370.872864, + "HyperDash": false + } + ] + }] +} \ No newline at end of file diff --git a/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/spinner-and-circles-expected-conversion.json b/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/spinner-and-circles-expected-conversion.json index dd81947f1c..abd5b2afd1 100644 --- a/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/spinner-and-circles-expected-conversion.json +++ b/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/spinner-and-circles-expected-conversion.json @@ -3,18 +3,21 @@ "StartTime": 2589, "Objects": [{ "StartTime": 2589, - "Position": 256 + "Position": 256, + "HyperDash": false }] }, { "StartTime": 2915, "Objects": [{ "StartTime": 2915, - "Position": 65 + "Position": 65, + "HyperDash": false }, { "StartTime": 2916, - "Position": 482 + "Position": 482, + "HyperDash": false } ] }, @@ -22,11 +25,13 @@ "StartTime": 3078, "Objects": [{ "StartTime": 3078, - "Position": 164 + "Position": 164, + "HyperDash": false }, { "StartTime": 3079, - "Position": 315 + "Position": 315, + "HyperDash": false } ] }, @@ -34,11 +39,13 @@ "StartTime": 3241, "Objects": [{ "StartTime": 3241, - "Position": 145 + "Position": 145, + "HyperDash": false }, { "StartTime": 3242, - "Position": 159 + "Position": 159, + "HyperDash": false } ] }, @@ -46,11 +53,13 @@ "StartTime": 3404, "Objects": [{ "StartTime": 3404, - "Position": 310 + "Position": 310, + "HyperDash": false }, { "StartTime": 3405, - "Position": 441 + "Position": 441, + "HyperDash": false } ] }, @@ -58,7 +67,8 @@ "StartTime": 5197, "Objects": [{ "StartTime": 5197, - "Position": 256 + "Position": 256, + "HyperDash": false }] } ] diff --git a/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/spinner-expected-conversion.json b/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/spinner-expected-conversion.json index b69b1ae056..8a7847e065 100644 --- a/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/spinner-expected-conversion.json +++ b/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/spinner-expected-conversion.json @@ -3,71 +3,88 @@ "StartTime": 18500, "Objects": [{ "StartTime": 18500, - "Position": 65 + "Position": 65, + "HyperDash": false }, { "StartTime": 18559, - "Position": 482 + "Position": 482, + "HyperDash": false }, { "StartTime": 18618, - "Position": 164 + "Position": 164, + "HyperDash": false }, { "StartTime": 18678, - "Position": 315 + "Position": 315, + "HyperDash": false }, { "StartTime": 18737, - "Position": 145 + "Position": 145, + "HyperDash": false }, { "StartTime": 18796, - "Position": 159 + "Position": 159, + "HyperDash": false }, { "StartTime": 18856, - "Position": 310 + "Position": 310, + "HyperDash": false }, { "StartTime": 18915, - "Position": 441 + "Position": 441, + "HyperDash": false }, { "StartTime": 18975, - "Position": 428 + "Position": 428, + "HyperDash": false }, { "StartTime": 19034, - "Position": 243 + "Position": 243, + "HyperDash": false }, { "StartTime": 19093, - "Position": 422 + "Position": 422, + "HyperDash": false }, { "StartTime": 19153, - "Position": 481 + "Position": 481, + "HyperDash": false }, { "StartTime": 19212, - "Position": 104 + "Position": 104, + "HyperDash": false }, { "StartTime": 19271, - "Position": 473 + "Position": 473, + "HyperDash": false }, { "StartTime": 19331, - "Position": 135 + "Position": 135, + "HyperDash": false }, { "StartTime": 19390, - "Position": 360 + "Position": 360, + "HyperDash": false }, { "StartTime": 19450, - "Position": 123 + "Position": 123, + "HyperDash": false } ] }] From 8a81392d2babc053a12a96139282cad63cf9ffbe Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 16 Dec 2021 19:26:36 +0900 Subject: [PATCH 142/146] Fix use of incorrect variable, add test --- .../CatchBeatmapConversionTest.cs | 3 ++- .../basic-hyperdash-expected-conversion.json | 19 +++++++++++++++++ .../Testing/Beatmaps/basic-hyperdash.osu | 21 +++++++++++++++++++ 3 files changed, 42 insertions(+), 1 deletion(-) create mode 100644 osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/basic-hyperdash-expected-conversion.json create mode 100644 osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/basic-hyperdash.osu diff --git a/osu.Game.Rulesets.Catch.Tests/CatchBeatmapConversionTest.cs b/osu.Game.Rulesets.Catch.Tests/CatchBeatmapConversionTest.cs index 3352982c20..be1885cfa6 100644 --- a/osu.Game.Rulesets.Catch.Tests/CatchBeatmapConversionTest.cs +++ b/osu.Game.Rulesets.Catch.Tests/CatchBeatmapConversionTest.cs @@ -27,6 +27,7 @@ namespace osu.Game.Rulesets.Catch.Tests [TestCase("hardrock-repeat-slider", new[] { typeof(CatchModHardRock) })] [TestCase("hardrock-spinner", new[] { typeof(CatchModHardRock) })] [TestCase("right-bound-hr-offset", new[] { typeof(CatchModHardRock) })] + [TestCase("basic-hyperdash")] public new void Test(string name, params Type[] mods) => base.Test(name, mods); protected override IEnumerable CreateConvertValue(HitObject hitObject) @@ -100,6 +101,6 @@ namespace osu.Game.Rulesets.Catch.Tests public bool Equals(ConvertValue other) => Precision.AlmostEquals(StartTime, other.StartTime, conversion_lenience) && Precision.AlmostEquals(Position, other.Position, conversion_lenience) - && HyperDash == other.hyperDash; + && HyperDash == other.HyperDash; } } diff --git a/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/basic-hyperdash-expected-conversion.json b/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/basic-hyperdash-expected-conversion.json new file mode 100644 index 0000000000..b2e9431f13 --- /dev/null +++ b/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/basic-hyperdash-expected-conversion.json @@ -0,0 +1,19 @@ +{ + "Mappings": [{ + "StartTime": 369, + "Objects": [{ + "StartTime": 369, + "Position": 0, + "HyperDash": true + }] + }, + { + "StartTime": 450, + "Objects": [{ + "StartTime": 450, + "Position": 512, + "HyperDash": false + }] + } + ] +} \ No newline at end of file diff --git a/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/basic-hyperdash.osu b/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/basic-hyperdash.osu new file mode 100644 index 0000000000..db07f8c30e --- /dev/null +++ b/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/basic-hyperdash.osu @@ -0,0 +1,21 @@ +osu file format v14 + +[General] +StackLeniency: 0.7 +Mode: 2 + +[Difficulty] +HPDrainRate:6 +CircleSize:4 +OverallDifficulty:9.6 +ApproachRate:9.6 +SliderMultiplier:1.9 +SliderTickRate:1 + +[TimingPoints] +2169,266.666666666667,4,2,1,70,1,0 + + +[HitObjects] +0,192,369,1,0,0:0:0:0: +512,192,450,1,0,0:0:0:0: From c08b6cf16050b73abfa8b290aec93ad95503f863 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 16 Dec 2021 19:53:22 +0900 Subject: [PATCH 143/146] Remove unnecessary `StartAsync` call on `TcpIpcProvider` --- osu.Desktop/Program.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Desktop/Program.cs b/osu.Desktop/Program.cs index a9e3575a49..7ec7d53a7e 100644 --- a/osu.Desktop/Program.cs +++ b/osu.Desktop/Program.cs @@ -90,7 +90,6 @@ namespace osu.Desktop Logger.Log("Starting legacy IPC provider..."); legacyIpc = new LegacyTcpIpcProvider(); legacyIpc.Bind(); - legacyIpc.StartAsync(); } catch (Exception ex) { From abb617a3df76c0ed685f9b84ae4abd4b3ea8be79 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 16 Dec 2021 19:57:24 +0900 Subject: [PATCH 144/146] Avoid blocking `Active` state propagation --- osu.Game.Rulesets.Osu/OsuInputManager.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/OsuInputManager.cs b/osu.Game.Rulesets.Osu/OsuInputManager.cs index f5fc3de381..5c6b907e42 100644 --- a/osu.Game.Rulesets.Osu/OsuInputManager.cs +++ b/osu.Game.Rulesets.Osu/OsuInputManager.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.ComponentModel; +using osu.Framework.Input; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; using osu.Framework.Input.StateChanges.Events; @@ -44,8 +45,10 @@ namespace osu.Game.Rulesets.Osu { if (!AllowUserCursorMovement) { - // Still allow for forwarding of the "touch" part, but block the positional data. - e = new TouchStateChangeEvent(e.State, e.Input, e.Touch, false, null); + // Still allow for forwarding of the "touch" part, but replace the positional data with that of the mouse. + // Primarily relied upon by the "autopilot" osu! mod. + var touch = new Touch(e.Touch.Source, CurrentState.Mouse.Position); + e = new TouchStateChangeEvent(e.State, e.Input, touch, e.IsActive, null); } return base.HandleMouseTouchStateChange(e); From eecb1ce9f55084d19eea03378b9cf6c34cfe9706 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 16 Dec 2021 20:20:02 +0900 Subject: [PATCH 145/146] Avoid applying mouse down effects to menu cursor when it isn't visible Closes #16114. --- osu.Game/Graphics/Cursor/MenuCursor.cs | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/osu.Game/Graphics/Cursor/MenuCursor.cs b/osu.Game/Graphics/Cursor/MenuCursor.cs index 3fa90e2330..8e272f637f 100644 --- a/osu.Game/Graphics/Cursor/MenuCursor.cs +++ b/osu.Game/Graphics/Cursor/MenuCursor.cs @@ -72,18 +72,21 @@ namespace osu.Game.Graphics.Cursor protected override bool OnMouseDown(MouseDownEvent e) { - // only trigger animation for main mouse buttons - activeCursor.Scale = new Vector2(1); - activeCursor.ScaleTo(0.90f, 800, Easing.OutQuint); - - activeCursor.AdditiveLayer.Alpha = 0; - activeCursor.AdditiveLayer.FadeInFromZero(800, Easing.OutQuint); - - if (cursorRotate.Value && dragRotationState != DragRotationState.Rotating) + if (State.Value == Visibility.Visible) { - // if cursor is already rotating don't reset its rotate origin - dragRotationState = DragRotationState.DragStarted; - positionMouseDown = e.MousePosition; + // only trigger animation for main mouse buttons + activeCursor.Scale = new Vector2(1); + activeCursor.ScaleTo(0.90f, 800, Easing.OutQuint); + + activeCursor.AdditiveLayer.Alpha = 0; + activeCursor.AdditiveLayer.FadeInFromZero(800, Easing.OutQuint); + + if (cursorRotate.Value && dragRotationState != DragRotationState.Rotating) + { + // if cursor is already rotating don't reset its rotate origin + dragRotationState = DragRotationState.DragStarted; + positionMouseDown = e.MousePosition; + } } return base.OnMouseDown(e); From 6bfe973fe5a17a75186ff8e2dad30f3d2d5dac26 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 17 Dec 2021 13:44:52 +0900 Subject: [PATCH 146/146] 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 1131203a95..563836d29b 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 feae990df7..0e8486cabc 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 27ac1bf647..42d8962c14 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -60,7 +60,7 @@ - + @@ -83,7 +83,7 @@ - +