From 131e64e56c14760ffc408993ca5416d44f863528 Mon Sep 17 00:00:00 2001 From: MBmasher Date: Fri, 12 Nov 2021 21:29:51 +1100 Subject: [PATCH 001/147] Add bonus based on opacity of hit objects --- .../Difficulty/OsuDifficultyCalculator.cs | 17 ++++---- .../Difficulty/OsuPerformanceCalculator.cs | 14 ------- .../Difficulty/Skills/Flashlight.cs | 39 +++++++++++++++++-- 3 files changed, 43 insertions(+), 27 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs index 558ddc16ef..19e92d4365 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs @@ -22,6 +22,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty { private const double difficulty_multiplier = 0.0675; private double hitWindowGreat; + private double preempt; public OsuDifficultyCalculator(Ruleset ruleset, WorkingBeatmap beatmap) : base(ruleset, beatmap) @@ -34,11 +35,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty return new OsuDifficultyAttributes { Mods = mods, Skills = skills }; double aimRating = Math.Sqrt(skills[0].DifficultyValue()) * difficulty_multiplier; - double aimRatingNoSliders = Math.Sqrt(skills[1].DifficultyValue()) * difficulty_multiplier; - double speedRating = Math.Sqrt(skills[2].DifficultyValue()) * difficulty_multiplier; - double flashlightRating = Math.Sqrt(skills[3].DifficultyValue()) * difficulty_multiplier; - - double sliderFactor = aimRating > 0 ? aimRatingNoSliders / aimRating : 1; + double speedRating = Math.Sqrt(skills[1].DifficultyValue()) * difficulty_multiplier; + double flashlightRating = Math.Sqrt(skills[2].DifficultyValue()) * difficulty_multiplier; if (mods.Any(h => h is OsuModRelax)) speedRating = 0.0; @@ -77,7 +75,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty AimStrain = aimRating, SpeedStrain = speedRating, FlashlightRating = flashlightRating, - SliderFactor = sliderFactor, ApproachRate = preempt > 1200 ? (1800 - preempt) / 120 : (1200 - preempt) / 150 + 5, OverallDifficulty = (80 - hitWindowGreat) / 6, DrainRate = drainRate, @@ -110,12 +107,13 @@ namespace osu.Game.Rulesets.Osu.Difficulty hitWindowGreat = hitWindows.WindowFor(HitResult.Great) / clockRate; + preempt = IBeatmapDifficultyInfo.DifficultyRange(beatmap.Difficulty.ApproachRate, 1800, 1200, 450) / clockRate; + return new Skill[] { - new Aim(mods, true), - new Aim(mods, false), + new Aim(mods), new Speed(mods, hitWindowGreat), - new Flashlight(mods) + new Flashlight(mods, preempt) }; } @@ -126,6 +124,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty new OsuModEasy(), new OsuModHardRock(), new OsuModFlashlight(), + new OsuModHidden(), }; } } diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs index 8d45c7a8cc..817b0b63a8 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs @@ -125,16 +125,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty aimValue *= 1.0 + 0.04 * (12.0 - Attributes.ApproachRate); } - // We assume 15% of sliders in a map are difficult since there's no way to tell from the performance calculator. - double estimateDifficultSliders = Attributes.SliderCount * 0.15; - - if (Attributes.SliderCount > 0) - { - double estimateSliderEndsDropped = Math.Clamp(Math.Min(countOk + countMeh + countMiss, Attributes.MaxCombo - scoreMaxCombo), 0, estimateDifficultSliders); - double sliderNerfFactor = (1 - Attributes.SliderFactor) * Math.Pow(1 - estimateSliderEndsDropped / estimateDifficultSliders, 3) + Attributes.SliderFactor; - aimValue *= sliderNerfFactor; - } - aimValue *= accuracy; // It is important to also consider accuracy difficulty when doing that. aimValue *= 0.98 + Math.Pow(Attributes.OverallDifficulty, 2) / 2500; @@ -234,10 +224,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty double flashlightValue = Math.Pow(rawFlashlight, 2.0) * 25.0; - // Add an additional bonus for HDFL. - if (mods.Any(h => h is OsuModHidden)) - flashlightValue *= 1.3; - // Penalize misses by assessing # of misses relative to the total # of objects. Default a 3% reduction for any # of misses. if (effectiveMissCount > 0) flashlightValue *= 0.97 * Math.Pow(1 - Math.Pow((double)effectiveMissCount / totalHits, 0.775), Math.Pow(effectiveMissCount, .875)); diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs index 466f0556ab..55ef8db129 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs @@ -2,8 +2,10 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Linq; using osu.Game.Rulesets.Difficulty.Preprocessing; using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Osu.Mods; using osu.Game.Rulesets.Osu.Difficulty.Preprocessing; using osu.Game.Rulesets.Osu.Objects; @@ -14,16 +16,24 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills /// public class Flashlight : OsuStrainSkill { - public Flashlight(Mod[] mods) + public Flashlight(Mod[] mods, double preemptTime) : base(mods) { + this.mods = mods; + this.preemptTime = preemptTime; } - private double skillMultiplier => 0.15; + private double skillMultiplier => 0.12; private double strainDecayBase => 0.15; protected override double DecayWeight => 1.0; protected override int HistoryLength => 10; // Look back for 10 notes is added for the sake of flashlight calculations. + private Mod[] mods; + private bool hidden; + private double preemptTime; + + private const double max_opacity_bonus = 0.4; + private double currentStrain; private double strainValueOf(DifficultyHitObject current) @@ -31,6 +41,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills if (current.BaseObject is Spinner) return 0; + hidden = mods.Any(m => m is OsuModHidden); + var osuCurrent = (OsuDifficultyHitObject)current; var osuHitObject = (OsuHitObject)(osuCurrent.BaseObject); @@ -58,11 +70,30 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills // 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); - result += Math.Pow(0.8, i) * stackNerf * scalingFactor * jumpDistance / cumulativeStrainTime; + // Bonus based on how visible the object is. + double opacityBonus = 1.0 + max_opacity_bonus * (1.0 - opacity(cumulativeStrainTime, preemptTime, hidden)); + + result += Math.Pow(0.8, i) * stackNerf * opacityBonus * scalingFactor * jumpDistance / cumulativeStrainTime; } } - return Math.Pow(smallDistNerf * result, 2.0); + result = Math.Pow(smallDistNerf * result, 2.0); + + if (hidden) { + result *= 1.0 + max_opacity_bonus; + } + + return result; + } + + private double opacity(double ms, double preemptTime, bool hidden) { + if (hidden) { + return Math.Clamp(Math.Min((1 - ms / preemptTime) * 2.5, (ms / preemptTime) * (1.0 / 0.3)), 0.0, 1.0); + } + else + { + return Math.Clamp((1.0 - ms / preemptTime) * 1.5, 0.0, 1.0); + } } private double strainDecay(double ms) => Math.Pow(strainDecayBase, ms / 1000); From 5a3be778a172405402bdcaadfdfdb7bb04ce190d Mon Sep 17 00:00:00 2001 From: MBmasher Date: Fri, 12 Nov 2021 21:41:01 +1100 Subject: [PATCH 002/147] Resolve conflicts with recent slider hotfix --- .../Difficulty/OsuDifficultyCalculator.cs | 15 ++++++++++----- .../Difficulty/OsuPerformanceCalculator.cs | 10 ++++++++++ .../Difficulty/Skills/Flashlight.cs | 2 +- 3 files changed, 21 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs index 19e92d4365..4e916af813 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs @@ -35,8 +35,11 @@ namespace osu.Game.Rulesets.Osu.Difficulty return new OsuDifficultyAttributes { Mods = mods, Skills = skills }; double aimRating = Math.Sqrt(skills[0].DifficultyValue()) * difficulty_multiplier; - double speedRating = Math.Sqrt(skills[1].DifficultyValue()) * difficulty_multiplier; - double flashlightRating = Math.Sqrt(skills[2].DifficultyValue()) * difficulty_multiplier; + double aimRatingNoSliders = Math.Sqrt(skills[1].DifficultyValue()) * difficulty_multiplier; + double speedRating = Math.Sqrt(skills[2].DifficultyValue()) * difficulty_multiplier; + double flashlightRating = Math.Sqrt(skills[3].DifficultyValue()) * difficulty_multiplier; + + double sliderFactor = aimRating > 0 ? aimRatingNoSliders / aimRating : 1; if (mods.Any(h => h is OsuModRelax)) speedRating = 0.0; @@ -75,6 +78,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty AimStrain = aimRating, SpeedStrain = speedRating, FlashlightRating = flashlightRating, + SliderFactor = sliderFactor, ApproachRate = preempt > 1200 ? (1800 - preempt) / 120 : (1200 - preempt) / 150 + 5, OverallDifficulty = (80 - hitWindowGreat) / 6, DrainRate = drainRate, @@ -111,7 +115,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty return new Skill[] { - new Aim(mods), + new Aim(mods, true), + new Aim(mods, false), new Speed(mods, hitWindowGreat), new Flashlight(mods, preempt) }; @@ -124,7 +129,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty new OsuModEasy(), new OsuModHardRock(), new OsuModFlashlight(), - new OsuModHidden(), + new OsuModHidden() }; } -} +} \ No newline at end of file diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs index 817b0b63a8..f4c451f80b 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs @@ -125,6 +125,16 @@ namespace osu.Game.Rulesets.Osu.Difficulty aimValue *= 1.0 + 0.04 * (12.0 - Attributes.ApproachRate); } + // We assume 15% of sliders in a map are difficult since there's no way to tell from the performance calculator. + double estimateDifficultSliders = Attributes.SliderCount * 0.15; + + if (Attributes.SliderCount > 0) + { + double estimateSliderEndsDropped = Math.Clamp(Math.Min(countOk + countMeh + countMiss, Attributes.MaxCombo - scoreMaxCombo), 0, estimateDifficultSliders); + double sliderNerfFactor = (1 - Attributes.SliderFactor) * Math.Pow(1 - estimateSliderEndsDropped / estimateDifficultSliders, 3) + Attributes.SliderFactor; + aimValue *= sliderNerfFactor; + } + aimValue *= accuracy; // It is important to also consider accuracy difficulty when doing that. aimValue *= 0.98 + Math.Pow(Attributes.OverallDifficulty, 2) / 2500; diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs index 55ef8db129..e0d2a084bd 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs @@ -1,4 +1,4 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. using System; From efac11e886f887673b36d5d42d840bc85ead9688 Mon Sep 17 00:00:00 2001 From: MBmasher Date: Fri, 12 Nov 2021 21:42:27 +1100 Subject: [PATCH 003/147] Add extra bonus for hidden+flashlight --- osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs index e0d2a084bd..caae53516d 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs @@ -23,7 +23,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills this.preemptTime = preemptTime; } - private double skillMultiplier => 0.12; + private double skillMultiplier => 0.11; private double strainDecayBase => 0.15; protected override double DecayWeight => 1.0; protected override int HistoryLength => 10; // Look back for 10 notes is added for the sake of flashlight calculations. @@ -33,6 +33,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills private double preemptTime; private const double max_opacity_bonus = 0.4; + private const double hidden_bonus = 0.3; private double currentStrain; @@ -81,6 +82,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills if (hidden) { result *= 1.0 + max_opacity_bonus; + // Additional bonus for Hidden due to there being no approach circles. + result *= 1.0 + hidden_bonus; } return result; From f2d05ea899ba2aaa93deb9680042c1ce6a5cba8f Mon Sep 17 00:00:00 2001 From: MBmasher Date: Wed, 17 Nov 2021 11:27:48 +1100 Subject: [PATCH 004/147] Remove strain being multiplied by max opacity bonus --- osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs index caae53516d..6d6f2b0f6c 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs @@ -80,11 +80,9 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills result = Math.Pow(smallDistNerf * result, 2.0); - if (hidden) { - result *= 1.0 + max_opacity_bonus; - // Additional bonus for Hidden due to there being no approach circles. + // Additional bonus for Hidden due to there being no approach circles. + if (hidden) result *= 1.0 + hidden_bonus; - } return result; } From 63c5f7d9d7b16309f045fa22faf39e4809a0a631 Mon Sep 17 00:00:00 2001 From: MBmasher Date: Wed, 17 Nov 2021 11:39:12 +1100 Subject: [PATCH 005/147] Balancing opacity and hidden bonus --- osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs index 6d6f2b0f6c..12580cb450 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs @@ -33,7 +33,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills private double preemptTime; private const double max_opacity_bonus = 0.4; - private const double hidden_bonus = 0.3; + private const double hidden_bonus = 0.4; private double currentStrain; From 6a444b9edbc2c8b780e965b08dcef1ade102e990 Mon Sep 17 00:00:00 2001 From: MBmasher Date: Thu, 18 Nov 2021 09:47:41 +1100 Subject: [PATCH 006/147] Further balancing opacity/hidden bonus --- .../Difficulty/Flashlight.cs | 112 ++++++++++++++++++ .../Difficulty/Skills/Flashlight.cs | 8 +- 2 files changed, 116 insertions(+), 4 deletions(-) create mode 100644 osu.Game.Rulesets.Osu/Difficulty/Flashlight.cs diff --git a/osu.Game.Rulesets.Osu/Difficulty/Flashlight.cs b/osu.Game.Rulesets.Osu/Difficulty/Flashlight.cs new file mode 100644 index 0000000000..da7ab3d1fc --- /dev/null +++ b/osu.Game.Rulesets.Osu/Difficulty/Flashlight.cs @@ -0,0 +1,112 @@ +// 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.Rulesets.Difficulty.Preprocessing; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Osu.Mods; +using osu.Game.Rulesets.Osu.Difficulty.Preprocessing; +using osu.Game.Rulesets.Osu.Objects; + +namespace osu.Game.Rulesets.Osu.Difficulty.Skills +{ + /// + /// Represents the skill required to memorise and hit every object in a map with the Flashlight mod enabled. + /// + public class Flashlight : OsuStrainSkill + { + public Flashlight(Mod[] mods, double preemptTime) + : base(mods) + { + this.mods = mods; + this.preemptTime = preemptTime; + } + + private double skillMultiplier => 0.07; + private double strainDecayBase => 0.15; + protected override double DecayWeight => 1.0; + protected override int HistoryLength => 10; // Look back for 10 notes is added for the sake of flashlight calculations. + + private Mod[] mods; + private bool hidden; + private double preemptTime; + + private const double max_opacity_bonus = 1.0; + private const double hidden_bonus = 0.8; + + private double currentStrain; + + private double strainValueOf(DifficultyHitObject current) + { + if (current.BaseObject is Spinner) + return 0; + + hidden = mods.Any(m => m is OsuModHidden); + + var osuCurrent = (OsuDifficultyHitObject)current; + var osuHitObject = (OsuHitObject)(osuCurrent.BaseObject); + + double scalingFactor = 52.0 / osuHitObject.Radius; + double smallDistNerf = 1.0; + double cumulativeStrainTime = 0.0; + + double result = 0.0; + + for (int i = 0; i < Previous.Count; i++) + { + var osuPrevious = (OsuDifficultyHitObject)Previous[i]; + var osuPreviousHitObject = (OsuHitObject)(osuPrevious.BaseObject); + + if (!(osuPrevious.BaseObject is Spinner)) + { + double jumpDistance = (osuHitObject.StackedPosition - osuPreviousHitObject.EndPosition).Length; + + cumulativeStrainTime += osuPrevious.StrainTime; + + // We want to nerf objects that can be easily seen within the Flashlight circle radius. + if (i == 0) + 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); + + // Bonus based on how visible the object is. + double opacityBonus = 1.0 + max_opacity_bonus * (1.0 - opacity(cumulativeStrainTime, preemptTime, hidden)); + + result += Math.Pow(0.8, i) * stackNerf * opacityBonus * scalingFactor * jumpDistance / cumulativeStrainTime; + } + } + + result = Math.Pow(smallDistNerf * result, 2.0); + + // Additional bonus for Hidden due to there being no approach circles. + if (hidden) + result *= 1.0 + hidden_bonus; + + return result; + } + + private double opacity(double ms, double preemptTime, bool hidden) { + if (hidden) { + return Math.Clamp(Math.Min((1 - ms / preemptTime) * 2.5, (ms / preemptTime) * (1.0 / 0.3)), 0.0, 1.0); + } + else + { + return Math.Clamp((1.0 - ms / preemptTime) * 1.5, 0.0, 1.0); + } + } + + private double strainDecay(double ms) => Math.Pow(strainDecayBase, ms / 1000); + + protected override double CalculateInitialStrain(double time) => currentStrain * strainDecay(time - Previous[0].StartTime); + + protected override double StrainValueAt(DifficultyHitObject current) + { + currentStrain *= strainDecay(current.DeltaTime); + currentStrain += strainValueOf(current) * skillMultiplier; + + return currentStrain; + } + } +} diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs index 12580cb450..209374714d 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs @@ -1,4 +1,4 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. using System; @@ -23,7 +23,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills this.preemptTime = preemptTime; } - private double skillMultiplier => 0.11; + private double skillMultiplier => 0.09; private double strainDecayBase => 0.15; protected override double DecayWeight => 1.0; protected override int HistoryLength => 10; // Look back for 10 notes is added for the sake of flashlight calculations. @@ -32,8 +32,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills private bool hidden; private double preemptTime; - private const double max_opacity_bonus = 0.4; - private const double hidden_bonus = 0.4; + private const double max_opacity_bonus = 0.7; + private const double hidden_bonus = 0.5; private double currentStrain; From 8e8571543d526ee218cceb1c56b0ae784bc870bc Mon Sep 17 00:00:00 2001 From: MBmasher Date: Thu, 18 Nov 2021 09:48:18 +1100 Subject: [PATCH 007/147] Removing unnecessary file --- .../Difficulty/Flashlight.cs | 112 ------------------ 1 file changed, 112 deletions(-) delete mode 100644 osu.Game.Rulesets.Osu/Difficulty/Flashlight.cs diff --git a/osu.Game.Rulesets.Osu/Difficulty/Flashlight.cs b/osu.Game.Rulesets.Osu/Difficulty/Flashlight.cs deleted file mode 100644 index da7ab3d1fc..0000000000 --- a/osu.Game.Rulesets.Osu/Difficulty/Flashlight.cs +++ /dev/null @@ -1,112 +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.Rulesets.Difficulty.Preprocessing; -using osu.Game.Rulesets.Mods; -using osu.Game.Rulesets.Osu.Mods; -using osu.Game.Rulesets.Osu.Difficulty.Preprocessing; -using osu.Game.Rulesets.Osu.Objects; - -namespace osu.Game.Rulesets.Osu.Difficulty.Skills -{ - /// - /// Represents the skill required to memorise and hit every object in a map with the Flashlight mod enabled. - /// - public class Flashlight : OsuStrainSkill - { - public Flashlight(Mod[] mods, double preemptTime) - : base(mods) - { - this.mods = mods; - this.preemptTime = preemptTime; - } - - private double skillMultiplier => 0.07; - private double strainDecayBase => 0.15; - protected override double DecayWeight => 1.0; - protected override int HistoryLength => 10; // Look back for 10 notes is added for the sake of flashlight calculations. - - private Mod[] mods; - private bool hidden; - private double preemptTime; - - private const double max_opacity_bonus = 1.0; - private const double hidden_bonus = 0.8; - - private double currentStrain; - - private double strainValueOf(DifficultyHitObject current) - { - if (current.BaseObject is Spinner) - return 0; - - hidden = mods.Any(m => m is OsuModHidden); - - var osuCurrent = (OsuDifficultyHitObject)current; - var osuHitObject = (OsuHitObject)(osuCurrent.BaseObject); - - double scalingFactor = 52.0 / osuHitObject.Radius; - double smallDistNerf = 1.0; - double cumulativeStrainTime = 0.0; - - double result = 0.0; - - for (int i = 0; i < Previous.Count; i++) - { - var osuPrevious = (OsuDifficultyHitObject)Previous[i]; - var osuPreviousHitObject = (OsuHitObject)(osuPrevious.BaseObject); - - if (!(osuPrevious.BaseObject is Spinner)) - { - double jumpDistance = (osuHitObject.StackedPosition - osuPreviousHitObject.EndPosition).Length; - - cumulativeStrainTime += osuPrevious.StrainTime; - - // We want to nerf objects that can be easily seen within the Flashlight circle radius. - if (i == 0) - 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); - - // Bonus based on how visible the object is. - double opacityBonus = 1.0 + max_opacity_bonus * (1.0 - opacity(cumulativeStrainTime, preemptTime, hidden)); - - result += Math.Pow(0.8, i) * stackNerf * opacityBonus * scalingFactor * jumpDistance / cumulativeStrainTime; - } - } - - result = Math.Pow(smallDistNerf * result, 2.0); - - // Additional bonus for Hidden due to there being no approach circles. - if (hidden) - result *= 1.0 + hidden_bonus; - - return result; - } - - private double opacity(double ms, double preemptTime, bool hidden) { - if (hidden) { - return Math.Clamp(Math.Min((1 - ms / preemptTime) * 2.5, (ms / preemptTime) * (1.0 / 0.3)), 0.0, 1.0); - } - else - { - return Math.Clamp((1.0 - ms / preemptTime) * 1.5, 0.0, 1.0); - } - } - - private double strainDecay(double ms) => Math.Pow(strainDecayBase, ms / 1000); - - protected override double CalculateInitialStrain(double time) => currentStrain * strainDecay(time - Previous[0].StartTime); - - protected override double StrainValueAt(DifficultyHitObject current) - { - currentStrain *= strainDecay(current.DeltaTime); - currentStrain += strainValueOf(current) * skillMultiplier; - - return currentStrain; - } - } -} From 92cf447180144a3b2a00fff624aacd531eda8406 Mon Sep 17 00:00:00 2001 From: MBmasher Date: Thu, 18 Nov 2021 10:32:41 +1100 Subject: [PATCH 008/147] Remove unnecessary braces --- osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs index 209374714d..d8e73ffad4 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs @@ -88,13 +88,10 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills } private double opacity(double ms, double preemptTime, bool hidden) { - if (hidden) { + if (hidden) return Math.Clamp(Math.Min((1 - ms / preemptTime) * 2.5, (ms / preemptTime) * (1.0 / 0.3)), 0.0, 1.0); - } else - { return Math.Clamp((1.0 - ms / preemptTime) * 1.5, 0.0, 1.0); - } } private double strainDecay(double ms) => Math.Pow(strainDecayBase, ms / 1000); From 30e18f16d9b6c42eec1b5cace8836cbc1151f7bf Mon Sep 17 00:00:00 2001 From: MBmasher Date: Thu, 18 Nov 2021 10:33:44 +1100 Subject: [PATCH 009/147] Change mods and preemptTime to readonly --- osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs index d8e73ffad4..3ae9c0eca9 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs @@ -28,9 +28,9 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills protected override double DecayWeight => 1.0; protected override int HistoryLength => 10; // Look back for 10 notes is added for the sake of flashlight calculations. - private Mod[] mods; + private readonly Mod[] mods; private bool hidden; - private double preemptTime; + private readonly double preemptTime; private const double max_opacity_bonus = 0.7; private const double hidden_bonus = 0.5; From f4b23f09607ca514c62e2da8f1e8b07a707f2036 Mon Sep 17 00:00:00 2001 From: MBmasher Date: Thu, 18 Nov 2021 10:37:07 +1100 Subject: [PATCH 010/147] Remove setting preempt in CreateDifficultyAttributes --- osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs index 4e916af813..fe20ce112e 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs @@ -60,7 +60,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty double starRating = basePerformance > 0.00001 ? Math.Cbrt(1.12) * 0.027 * (Math.Cbrt(100000 / Math.Pow(2, 1 / 1.1) * basePerformance) + 4) : 0; - double preempt = IBeatmapDifficultyInfo.DifficultyRange(beatmap.Difficulty.ApproachRate, 1800, 1200, 450) / clockRate; double drainRate = beatmap.Difficulty.DrainRate; int maxCombo = beatmap.HitObjects.Count; From fe83b8fc77c8e247bcf48fe597f35a4c381b49c3 Mon Sep 17 00:00:00 2001 From: MBmasher Date: Thu, 18 Nov 2021 10:50:32 +1100 Subject: [PATCH 011/147] Add line break --- osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs index 3ae9c0eca9..b45a54f9e7 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs @@ -87,7 +87,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills return result; } - private double opacity(double ms, double preemptTime, bool hidden) { + private double opacity(double ms, double preemptTime, bool hidden) + { if (hidden) return Math.Clamp(Math.Min((1 - ms / preemptTime) * 2.5, (ms / preemptTime) * (1.0 / 0.3)), 0.0, 1.0); else From afbec941249b1d309ed31be7fcd0e84e180805f6 Mon Sep 17 00:00:00 2001 From: MBmasher Date: Sun, 21 Nov 2021 23:40:15 +1100 Subject: [PATCH 012/147] Move opacity function to OsuDifficultyHitObject --- .../Preprocessing/OsuDifficultyHitObject.cs | 11 +++++++++++ osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs | 10 +--------- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs index d073d751d0..dc8188929a 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs @@ -56,12 +56,14 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing private readonly OsuHitObject lastLastObject; private readonly OsuHitObject lastObject; + private readonly double clockRate; public OsuDifficultyHitObject(HitObject hitObject, HitObject lastLastObject, HitObject lastObject, double clockRate) : base(hitObject, lastObject, clockRate) { this.lastLastObject = (OsuHitObject)lastLastObject; this.lastObject = (OsuHitObject)lastObject; + this.clockRate = clockRate; // Capped to 25ms to prevent difficulty calculation breaking from simultaneous objects. StrainTime = Math.Max(DeltaTime, min_delta_time); @@ -69,6 +71,15 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing setDistances(clockRate); } + public double opacity(double ms, bool hidden) + { + double preemptTime = BaseObject.TimePreempt / clockRate; + if (hidden) + return Math.Clamp(Math.Min((1 - ms / preemptTime) * 2.5, (ms / preemptTime) * (1.0 / 0.3)), 0.0, 1.0); + else + return Math.Clamp((1.0 - ms / preemptTime) * 1.5, 0.0, 1.0); + } + private void setDistances(double clockRate) { // We don't need to calculate either angle or distance when one of the last->curr objects is a spinner diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs index b45a54f9e7..701670974b 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs @@ -72,7 +72,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills double stackNerf = Math.Min(1.0, (osuPrevious.JumpDistance / scalingFactor) / 25.0); // Bonus based on how visible the object is. - double opacityBonus = 1.0 + max_opacity_bonus * (1.0 - opacity(cumulativeStrainTime, preemptTime, hidden)); + double opacityBonus = 1.0 + max_opacity_bonus * (1.0 - osuCurrent.opacity(cumulativeStrainTime, hidden)); result += Math.Pow(0.8, i) * stackNerf * opacityBonus * scalingFactor * jumpDistance / cumulativeStrainTime; } @@ -87,14 +87,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills return result; } - private double opacity(double ms, double preemptTime, bool hidden) - { - if (hidden) - return Math.Clamp(Math.Min((1 - ms / preemptTime) * 2.5, (ms / preemptTime) * (1.0 / 0.3)), 0.0, 1.0); - else - return Math.Clamp((1.0 - ms / preemptTime) * 1.5, 0.0, 1.0); - } - private double strainDecay(double ms) => Math.Pow(strainDecayBase, ms / 1000); protected override double CalculateInitialStrain(double time) => currentStrain * strainDecay(time - Previous[0].StartTime); From a57c277a585573b3081f6834b1cab6f09be60cf8 Mon Sep 17 00:00:00 2001 From: MBmasher Date: Sun, 21 Nov 2021 23:43:09 +1100 Subject: [PATCH 013/147] Move preempt back to CreateDifficultyAttributes --- osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs | 6 ++---- osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs | 4 +--- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs index fe20ce112e..14101f8302 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs @@ -22,7 +22,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty { private const double difficulty_multiplier = 0.0675; private double hitWindowGreat; - private double preempt; public OsuDifficultyCalculator(Ruleset ruleset, WorkingBeatmap beatmap) : base(ruleset, beatmap) @@ -60,6 +59,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty double starRating = basePerformance > 0.00001 ? Math.Cbrt(1.12) * 0.027 * (Math.Cbrt(100000 / Math.Pow(2, 1 / 1.1) * basePerformance) + 4) : 0; + double preempt = IBeatmapDifficultyInfo.DifficultyRange(beatmap.Difficulty.ApproachRate, 1800, 1200, 450) / clockRate; double drainRate = beatmap.Difficulty.DrainRate; int maxCombo = beatmap.HitObjects.Count; @@ -110,14 +110,12 @@ namespace osu.Game.Rulesets.Osu.Difficulty hitWindowGreat = hitWindows.WindowFor(HitResult.Great) / clockRate; - preempt = IBeatmapDifficultyInfo.DifficultyRange(beatmap.Difficulty.ApproachRate, 1800, 1200, 450) / clockRate; - return new Skill[] { new Aim(mods, true), new Aim(mods, false), new Speed(mods, hitWindowGreat), - new Flashlight(mods, preempt) + new Flashlight(mods) }; } diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs index 701670974b..8969e95aba 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs @@ -16,11 +16,10 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills /// public class Flashlight : OsuStrainSkill { - public Flashlight(Mod[] mods, double preemptTime) + public Flashlight(Mod[] mods) : base(mods) { this.mods = mods; - this.preemptTime = preemptTime; } private double skillMultiplier => 0.09; @@ -30,7 +29,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills private readonly Mod[] mods; private bool hidden; - private readonly double preemptTime; private const double max_opacity_bonus = 0.7; private const double hidden_bonus = 0.5; From e9a4ee68004b998264f6577fe79050b276b7c1a0 Mon Sep 17 00:00:00 2001 From: MBmasher Date: Sun, 21 Nov 2021 23:53:40 +1100 Subject: [PATCH 014/147] Cleaning up code --- .../Difficulty/Preprocessing/OsuDifficultyHitObject.cs | 3 ++- osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs index dc8188929a..cc699aa3f9 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs @@ -71,9 +71,10 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing setDistances(clockRate); } - public double opacity(double ms, bool hidden) + public double Opacity(double ms, bool hidden) { double preemptTime = BaseObject.TimePreempt / clockRate; + if (hidden) return Math.Clamp(Math.Min((1 - ms / preemptTime) * 2.5, (ms / preemptTime) * (1.0 / 0.3)), 0.0, 1.0); else diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs index 8969e95aba..2523f66bf6 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs @@ -70,7 +70,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills double stackNerf = Math.Min(1.0, (osuPrevious.JumpDistance / scalingFactor) / 25.0); // Bonus based on how visible the object is. - double opacityBonus = 1.0 + max_opacity_bonus * (1.0 - osuCurrent.opacity(cumulativeStrainTime, hidden)); + double opacityBonus = 1.0 + max_opacity_bonus * (1.0 - osuCurrent.Opacity(cumulativeStrainTime, hidden)); result += Math.Pow(0.8, i) * stackNerf * opacityBonus * scalingFactor * jumpDistance / cumulativeStrainTime; } From e9745a3ac41c93ec5860505cf833ec5618c5a177 Mon Sep 17 00:00:00 2001 From: MBmasher Date: Mon, 22 Nov 2021 08:32:35 +1100 Subject: [PATCH 015/147] Fix wrong opacity formula --- .../Difficulty/Preprocessing/OsuDifficultyHitObject.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs index cc699aa3f9..4109c068ea 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs @@ -74,11 +74,12 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing public double Opacity(double ms, bool hidden) { double preemptTime = BaseObject.TimePreempt / clockRate; + double fadeInTime = BaseObject.TimeFadeIn / clockRate; if (hidden) - return Math.Clamp(Math.Min((1 - ms / preemptTime) * 2.5, (ms / preemptTime) * (1.0 / 0.3)), 0.0, 1.0); + return Math.Clamp(Math.Min((1.0 - ms / preemptTime) * 2.5, (ms / preemptTime - 0.3) * (1.0 / 0.3)), 0.0, 1.0); else - return Math.Clamp((1.0 - ms / preemptTime) * 1.5, 0.0, 1.0); + return Math.Clamp((preemptTime - ms) / fadeInTime, 0.0, 1.0); } private void setDistances(double clockRate) From 7833fab02d0f1e3add202aa2f24118b67f4f404d Mon Sep 17 00:00:00 2001 From: MBmasher Date: Mon, 22 Nov 2021 08:41:56 +1100 Subject: [PATCH 016/147] Balancing bonuses to adjust for corrected opacity formula --- osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs index 2523f66bf6..7518364dd1 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs @@ -22,7 +22,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills this.mods = mods; } - private double skillMultiplier => 0.09; + private double skillMultiplier => 0.11; private double strainDecayBase => 0.15; protected override double DecayWeight => 1.0; protected override int HistoryLength => 10; // Look back for 10 notes is added for the sake of flashlight calculations. @@ -30,8 +30,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills private readonly Mod[] mods; private bool hidden; - private const double max_opacity_bonus = 0.7; - private const double hidden_bonus = 0.5; + private const double max_opacity_bonus = 0.5; + private const double hidden_bonus = 0.1; private double currentStrain; From 65ef03034187ed9e65e288b1eee96d030eef145f Mon Sep 17 00:00:00 2001 From: MBmasher Date: Mon, 22 Nov 2021 08:59:41 +1100 Subject: [PATCH 017/147] Further balancing --- osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs index 7518364dd1..7b4119d354 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs @@ -22,7 +22,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills this.mods = mods; } - private double skillMultiplier => 0.11; + private double skillMultiplier => 0.1; private double strainDecayBase => 0.15; protected override double DecayWeight => 1.0; protected override int HistoryLength => 10; // Look back for 10 notes is added for the sake of flashlight calculations. @@ -30,8 +30,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills private readonly Mod[] mods; private bool hidden; - private const double max_opacity_bonus = 0.5; - private const double hidden_bonus = 0.1; + private const double max_opacity_bonus = 0.4; + private const double hidden_bonus = 0.2; private double currentStrain; From 383bf7cdfc7f71d3528ad054fcfd7c16aef751ff Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 30 Nov 2021 10:39:48 +0900 Subject: [PATCH 018/147] Only allow HD combination alongside FL --- osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs index 1b6eb2915b..79314344ea 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs @@ -125,7 +125,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty new OsuModEasy(), new OsuModHardRock(), new OsuModFlashlight(), - new OsuModHidden() + new MultiMod(new OsuModFlashlight(), new OsuModHidden()) }; } -} \ No newline at end of file +} From b0dc8bf0616cbf5ac9cb07edc8f26a7b33845223 Mon Sep 17 00:00:00 2001 From: MBmasher Date: Tue, 30 Nov 2021 12:58:49 +1100 Subject: [PATCH 019/147] Change Opacity function to take in absolute map time rather than relative time --- .../Difficulty/Preprocessing/OsuDifficultyHitObject.cs | 5 ++++- osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs index 4109c068ea..c94fd10db5 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs @@ -71,8 +71,11 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing setDistances(clockRate); } - public double Opacity(double ms, bool hidden) + public double Opacity(double T, bool hidden) { + double ms = (BaseObject.StartTime - T) / clockRate; + if (ms < 0) + return 0.0; double preemptTime = BaseObject.TimePreempt / clockRate; double fadeInTime = BaseObject.TimeFadeIn / clockRate; diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs index 7b4119d354..d7f515602e 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs @@ -70,7 +70,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills double stackNerf = Math.Min(1.0, (osuPrevious.JumpDistance / scalingFactor) / 25.0); // Bonus based on how visible the object is. - double opacityBonus = 1.0 + max_opacity_bonus * (1.0 - osuCurrent.Opacity(cumulativeStrainTime, hidden)); + double opacityBonus = 1.0 + max_opacity_bonus * (1.0 - osuCurrent.Opacity(osuPreviousHitObject.StartTime, hidden)); result += Math.Pow(0.8, i) * stackNerf * opacityBonus * scalingFactor * jumpDistance / cumulativeStrainTime; } From 3339afd6485270b4b3ac9db1d1dbffe599dc31da Mon Sep 17 00:00:00 2001 From: MBmasher Date: Tue, 30 Nov 2021 13:52:58 +1100 Subject: [PATCH 020/147] Change input variable name in Opacity function --- .../Difficulty/Preprocessing/OsuDifficultyHitObject.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs index c94fd10db5..7ab12f109f 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs @@ -71,9 +71,9 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing setDistances(clockRate); } - public double Opacity(double T, bool hidden) + public double Opacity(double mapTime, bool hidden) { - double ms = (BaseObject.StartTime - T) / clockRate; + double ms = (BaseObject.StartTime - mapTime) / clockRate; if (ms < 0) return 0.0; double preemptTime = BaseObject.TimePreempt / clockRate; From 5884b058b9f15ab2d518316d5e62c690431367b0 Mon Sep 17 00:00:00 2001 From: MBmasher Date: Tue, 30 Nov 2021 13:54:41 +1100 Subject: [PATCH 021/147] Add blank line --- .../Difficulty/Preprocessing/OsuDifficultyHitObject.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs index 7ab12f109f..a497c95990 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs @@ -76,6 +76,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing double ms = (BaseObject.StartTime - mapTime) / clockRate; if (ms < 0) return 0.0; + double preemptTime = BaseObject.TimePreempt / clockRate; double fadeInTime = BaseObject.TimeFadeIn / clockRate; From 9824d805eac344ab727d87d2b269ac0fca34d6b2 Mon Sep 17 00:00:00 2001 From: MBmasher Date: Tue, 30 Nov 2021 14:36:38 +1100 Subject: [PATCH 022/147] Remove unnecessary clockRate in Opacity function --- .../Difficulty/Preprocessing/OsuDifficultyHitObject.cs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs index a497c95990..dacbb4c51f 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs @@ -56,14 +56,12 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing private readonly OsuHitObject lastLastObject; private readonly OsuHitObject lastObject; - private readonly double clockRate; public OsuDifficultyHitObject(HitObject hitObject, HitObject lastLastObject, HitObject lastObject, double clockRate) : base(hitObject, lastObject, clockRate) { this.lastLastObject = (OsuHitObject)lastLastObject; this.lastObject = (OsuHitObject)lastObject; - this.clockRate = clockRate; // Capped to 25ms to prevent difficulty calculation breaking from simultaneous objects. StrainTime = Math.Max(DeltaTime, min_delta_time); @@ -73,12 +71,12 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing public double Opacity(double mapTime, bool hidden) { - double ms = (BaseObject.StartTime - mapTime) / clockRate; + double ms = BaseObject.StartTime - mapTime; if (ms < 0) return 0.0; - double preemptTime = BaseObject.TimePreempt / clockRate; - double fadeInTime = BaseObject.TimeFadeIn / clockRate; + double preemptTime = BaseObject.TimePreempt; + double fadeInTime = BaseObject.TimeFadeIn; if (hidden) return Math.Clamp(Math.Min((1.0 - ms / preemptTime) * 2.5, (ms / preemptTime - 0.3) * (1.0 / 0.3)), 0.0, 1.0); From a7aea49cb38be1108d91bcc884a25b9526019ee4 Mon Sep 17 00:00:00 2001 From: MBmasher Date: Tue, 21 Dec 2021 20:06:07 +1100 Subject: [PATCH 023/147] Rename `osuPreviousHitObject` to `currentHitObject` --- osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs index 76ce6dc057..1a707b26b5 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs @@ -73,7 +73,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills double stackNerf = Math.Min(1.0, (currentObj.LazyJumpDistance / scalingFactor) / 25.0); // Bonus based on how visible the object is. - double opacityBonus = 1.0 + max_opacity_bonus * (1.0 - osuCurrent.Opacity(osuPreviousHitObject.StartTime, hidden)); + double opacityBonus = 1.0 + max_opacity_bonus * (1.0 - osuCurrent.Opacity(currentHitObject.StartTime, hidden)); result += stackNerf * scalingFactor * jumpDistance / cumulativeStrainTime; } From 3d3de00581648712338aa5237926a143f8275710 Mon Sep 17 00:00:00 2001 From: MBmasher Date: Tue, 21 Dec 2021 20:06:53 +1100 Subject: [PATCH 024/147] Move `hidden` initialisation to Flashlight constructor --- osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs index 1a707b26b5..3b23ec505b 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs @@ -20,6 +20,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills : base(mods) { this.mods = mods; + this.hidden = mods.Any(m => m is OsuModHidden); } private double skillMultiplier => 0.1; @@ -40,8 +41,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills if (current.BaseObject is Spinner) return 0; - hidden = mods.Any(m => m is OsuModHidden); - var osuCurrent = (OsuDifficultyHitObject)current; var osuHitObject = (OsuHitObject)(osuCurrent.BaseObject); From 5d8968498cacbadfa704f4aa730652d6a6709228 Mon Sep 17 00:00:00 2001 From: MBmasher Date: Tue, 21 Dec 2021 20:08:57 +1100 Subject: [PATCH 025/147] Adjust `skillMultiplier` after merging #15728, #15867 --- osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs index 3b23ec505b..266f4e8960 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs @@ -23,7 +23,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills this.hidden = mods.Any(m => m is OsuModHidden); } - private double skillMultiplier => 0.1; + private double skillMultiplier => 0.05; private double strainDecayBase => 0.15; protected override double DecayWeight => 1.0; protected override int HistoryLength => 10; // Look back for 10 notes is added for the sake of flashlight calculations. From c5de203aa5258b66a2b7067c6306c12b75e158f3 Mon Sep 17 00:00:00 2001 From: MBmasher Date: Tue, 21 Dec 2021 20:10:19 +1100 Subject: [PATCH 026/147] Multiply `opacityBonus` to base strain --- osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs index 266f4e8960..8b2e66e0a7 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs @@ -74,7 +74,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills // Bonus based on how visible the object is. double opacityBonus = 1.0 + max_opacity_bonus * (1.0 - osuCurrent.Opacity(currentHitObject.StartTime, hidden)); - result += stackNerf * scalingFactor * jumpDistance / cumulativeStrainTime; + result += stackNerf * opacityBonus * scalingFactor * jumpDistance / cumulativeStrainTime; } lastObj = currentObj; From 2aafcd3628463ccd2c5c0c8c88b32495ebc8694c Mon Sep 17 00:00:00 2001 From: MBmasher Date: Tue, 21 Dec 2021 20:58:05 +1100 Subject: [PATCH 027/147] Refactor code regarding `hidden` boolean --- osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs index 8b2e66e0a7..5d2052046b 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs @@ -19,8 +19,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills public Flashlight(Mod[] mods) : base(mods) { - this.mods = mods; - this.hidden = mods.Any(m => m is OsuModHidden); + hidden = mods.Any(m => m is OsuModHidden); } private double skillMultiplier => 0.05; @@ -28,8 +27,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills protected override double DecayWeight => 1.0; protected override int HistoryLength => 10; // Look back for 10 notes is added for the sake of flashlight calculations. - private readonly Mod[] mods; - private bool hidden; + private readonly bool hidden; private const double max_opacity_bonus = 0.4; private const double hidden_bonus = 0.2; From 172d14bcc6675e042b7852845c6122222530c4c5 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Sun, 8 May 2022 11:40:14 -0700 Subject: [PATCH 028/147] Improve code quality of mod column flow container --- osu.Game/Overlays/Mods/ModSelectScreen.cs | 29 ++++------------------- 1 file changed, 4 insertions(+), 25 deletions(-) diff --git a/osu.Game/Overlays/Mods/ModSelectScreen.cs b/osu.Game/Overlays/Mods/ModSelectScreen.cs index fc06af3f9d..b232acf11b 100644 --- a/osu.Game/Overlays/Mods/ModSelectScreen.cs +++ b/osu.Game/Overlays/Mods/ModSelectScreen.cs @@ -12,7 +12,6 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Input.Events; -using osu.Framework.Layout; using osu.Framework.Lists; using osu.Framework.Utils; using osu.Game.Audio; @@ -125,12 +124,15 @@ namespace osu.Game.Overlays.Mods ScrollbarOverlapsContent = false, Child = columnFlow = new ColumnFlowContainer { + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, Direction = FillDirection.Horizontal, Shear = new Vector2(SHEAR, 0), RelativeSizeAxes = Axes.Y, AutoSizeAxes = Axes.X, Spacing = new Vector2(10, 0), Margin = new MarginPadding { Horizontal = 70 }, + Padding = new MarginPadding { Bottom = 10 }, Children = new[] { createModColumnContent(ModType.DifficultyReduction, new[] { Key.Q, Key.W, Key.E, Key.R, Key.T, Key.Y, Key.U, Key.I, Key.O, Key.P }), @@ -490,19 +492,12 @@ namespace osu.Game.Overlays.Mods } /// - /// Manages padding and layout of mod columns. + /// Manages layout of mod columns. /// internal class ColumnFlowContainer : FillFlowContainer { public IEnumerable Columns => Children.Select(dimWrapper => dimWrapper.Column); - private readonly LayoutValue drawSizeLayout = new LayoutValue(Invalidation.DrawSize); - - public ColumnFlowContainer() - { - AddLayout(drawSizeLayout); - } - public override void Add(ColumnDimContainer dimContainer) { base.Add(dimContainer); @@ -510,22 +505,6 @@ namespace osu.Game.Overlays.Mods Debug.Assert(dimContainer != null); dimContainer.Column.Shear = Vector2.Zero; } - - protected override void Update() - { - base.Update(); - - if (!drawSizeLayout.IsValid) - { - Padding = new MarginPadding - { - Left = DrawHeight * SHEAR, - Bottom = 10 - }; - - drawSizeLayout.Validate(); - } - } } /// From a16f2349aa6c9b2bf4246e1fc970dcebf5ada2e4 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Mon, 9 May 2022 17:55:40 +0900 Subject: [PATCH 029/147] Fix next queued item not selecting after gameplay --- .../TestSceneMultiplayerMatchSubScreen.cs | 37 +++++++++++++++++++ .../OnlinePlay/DrawableRoomPlaylistItem.cs | 8 ++-- 2 files changed, 41 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs index 2abde82e92..6173580f0b 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs @@ -21,6 +21,7 @@ using osu.Game.Rulesets.Osu.Mods; using osu.Game.Rulesets.Taiko; using osu.Game.Rulesets.Taiko.Mods; using osu.Game.Rulesets.UI; +using osu.Game.Screens.OnlinePlay; using osu.Game.Screens.OnlinePlay.Match; using osu.Game.Screens.OnlinePlay.Multiplayer; using osu.Game.Screens.OnlinePlay.Multiplayer.Match; @@ -176,5 +177,41 @@ namespace osu.Game.Tests.Visual.Multiplayer .ChildrenOfType() .SingleOrDefault(panel => !panel.Filtered.Value)?.Mod is OsuModDoubleTime); } + + [Test] + public void TestNextPlaylistItemSelectedAfterCompletion() + { + AddStep("add two playlist items", () => + { + SelectedRoom.Value.Playlist.AddRange(new[] + { + new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First()).BeatmapInfo) + { + RulesetID = new OsuRuleset().RulesetInfo.OnlineID + }, + new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First()).BeatmapInfo) + { + RulesetID = new OsuRuleset().RulesetInfo.OnlineID + } + }); + }); + + ClickButtonWhenEnabled(); + + AddUntilStep("wait for join", () => RoomJoined); + + ClickButtonWhenEnabled(); + ClickButtonWhenEnabled(); + + AddStep("change user to loaded", () => MultiplayerClient.ChangeState(MultiplayerUserState.Loaded)); + AddUntilStep("user playing", () => MultiplayerClient.LocalUser?.State == MultiplayerUserState.Playing); + AddStep("abort gameplay", () => MultiplayerClient.AbortGameplay()); + + AddUntilStep("last playlist item selected", () => + { + var lastItem = this.ChildrenOfType().Single(p => p.Item.ID == MultiplayerClient.APIRoom?.Playlist.Last().ID); + return lastItem.IsSelectedItem; + }); + } } } diff --git a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs index 459b861d96..2618e15d31 100644 --- a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs +++ b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs @@ -65,6 +65,8 @@ namespace osu.Game.Screens.OnlinePlay public readonly PlaylistItem Item; + public bool IsSelectedItem => SelectedItem.Value?.ID == Item.ID; + private readonly DelayedLoadWrapper onScreenLoader = new DelayedLoadWrapper(Empty) { RelativeSizeAxes = Axes.Both }; private readonly IBindable valid = new Bindable(); @@ -128,12 +130,10 @@ namespace osu.Game.Screens.OnlinePlay SelectedItem.BindValueChanged(selected => { - bool isCurrent = selected.NewValue == Model; - if (!valid.Value) { // Don't allow selection when not valid. - if (isCurrent) + if (IsSelectedItem) { SelectedItem.Value = selected.OldValue; } @@ -142,7 +142,7 @@ namespace osu.Game.Screens.OnlinePlay return; } - maskingContainer.BorderThickness = isCurrent ? 5 : 0; + maskingContainer.BorderThickness = IsSelectedItem ? 5 : 0; }, true); valid.BindValueChanged(_ => Scheduler.AddOnce(refresh)); From 0fe121f48a8954d25fef2b8059fea1f60d967549 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 9 May 2022 13:25:22 +0300 Subject: [PATCH 030/147] Move and rename settings toolbox group test scene --- .../TestSceneSettingsToolboxGroup.cs} | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) rename osu.Game.Tests/Visual/{Gameplay/TestSceneReplaySettingsOverlay.cs => UserInterface/TestSceneSettingsToolboxGroup.cs} (85%) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneReplaySettingsOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneSettingsToolboxGroup.cs similarity index 85% rename from osu.Game.Tests/Visual/Gameplay/TestSceneReplaySettingsOverlay.cs rename to osu.Game.Tests/Visual/UserInterface/TestSceneSettingsToolboxGroup.cs index f8fab784cc..5ca30d07b0 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneReplaySettingsOverlay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneSettingsToolboxGroup.cs @@ -1,4 +1,4 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. using NUnit.Framework; @@ -8,12 +8,12 @@ using osu.Game.Graphics.UserInterface; using osu.Game.Screens.Play.HUD; using osu.Game.Screens.Play.PlayerSettings; -namespace osu.Game.Tests.Visual.Gameplay +namespace osu.Game.Tests.Visual.UserInterface { [TestFixture] - public class TestSceneReplaySettingsOverlay : OsuTestScene + public class TestSceneSettingsToolboxGroup : OsuTestScene { - public TestSceneReplaySettingsOverlay() + public TestSceneSettingsToolboxGroup() { ExampleContainer container; From deda1c83e67779a748d82138215846519dbcfea7 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 9 May 2022 13:44:18 +0300 Subject: [PATCH 031/147] Add failing test case --- .../TestSceneSettingsToolboxGroup.cs | 22 ++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneSettingsToolboxGroup.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneSettingsToolboxGroup.cs index 5ca30d07b0..76657afbcd 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneSettingsToolboxGroup.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneSettingsToolboxGroup.cs @@ -1,17 +1,21 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Linq; using NUnit.Framework; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Testing; using osu.Game.Graphics.UserInterface; +using osu.Game.Overlays; using osu.Game.Screens.Play.HUD; using osu.Game.Screens.Play.PlayerSettings; +using osuTK.Input; namespace osu.Game.Tests.Visual.UserInterface { [TestFixture] - public class TestSceneSettingsToolboxGroup : OsuTestScene + public class TestSceneSettingsToolboxGroup : OsuManualInputManagerTestScene { public TestSceneSettingsToolboxGroup() { @@ -46,6 +50,22 @@ namespace osu.Game.Tests.Visual.UserInterface })); } + [Test] + public void TestClickExpandButtonMultipleTimes() + { + SettingsToolboxGroup group = null; + + AddAssert("group expanded by default", () => (group = this.ChildrenOfType().First()).Expanded.Value); + AddStep("click expand button multiple times", () => + { + InputManager.MoveMouseTo(group.ChildrenOfType().Single()); + Scheduler.AddDelayed(() => InputManager.Click(MouseButton.Left), 100); + Scheduler.AddDelayed(() => InputManager.Click(MouseButton.Left), 200); + Scheduler.AddDelayed(() => InputManager.Click(MouseButton.Left), 300); + }); + AddAssert("group contracted", () => !group.Expanded.Value); + } + private class ExampleContainer : PlayerSettingsGroup { public ExampleContainer() From 702c6ae658d671a21985f28ec199b3bc18d7f5bd Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 9 May 2022 13:49:31 +0300 Subject: [PATCH 032/147] Fix `SettingsToolboxGroup` not clearing transforms before updating autosize --- osu.Game/Overlays/SettingsToolboxGroup.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Overlays/SettingsToolboxGroup.cs b/osu.Game/Overlays/SettingsToolboxGroup.cs index 808d4fc422..36d4f03e02 100644 --- a/osu.Game/Overlays/SettingsToolboxGroup.cs +++ b/osu.Game/Overlays/SettingsToolboxGroup.cs @@ -156,6 +156,8 @@ namespace osu.Game.Overlays private void updateExpandedState(ValueChangedEvent expanded) { + content.ClearTransforms(); + if (expanded.NewValue) content.AutoSizeAxes = Axes.Y; else From 70a90722e8dac2147feaffb764064a65ae5d031c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 9 May 2022 19:48:53 +0900 Subject: [PATCH 033/147] Fix dropdown item hover colour not being set in time Turns out to be an osu!-side issue. The colour transform was being shortcutted for the non-displayed case, which meant it was not in a good state in time for the first hover. Closes https://github.com/ppy/osu/issues/18163#issuecomment-1120747301 --- osu.Game/Graphics/UserInterface/OsuDropdown.cs | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/OsuDropdown.cs b/osu.Game/Graphics/UserInterface/OsuDropdown.cs index b1d4691938..4e391c8221 100644 --- a/osu.Game/Graphics/UserInterface/OsuDropdown.cs +++ b/osu.Game/Graphics/UserInterface/OsuDropdown.cs @@ -184,14 +184,12 @@ namespace osu.Game.Graphics.UserInterface protected override void UpdateBackgroundColour() { - if (!IsPreSelected && !IsSelected) - { - Background.FadeOut(600, Easing.OutQuint); - return; - } - - Background.FadeIn(100, Easing.OutQuint); Background.FadeColour(IsPreSelected ? BackgroundColourHover : BackgroundColourSelected, 100, Easing.OutQuint); + + if (IsPreSelected || IsSelected) + Background.FadeIn(100, Easing.OutQuint); + else + Background.FadeOut(600, Easing.OutQuint); } protected override void UpdateForegroundColour() From 5f3bea846b0e2fe0c435629c9ee056e6b86d3742 Mon Sep 17 00:00:00 2001 From: Jai Sharma Date: Mon, 9 May 2022 11:15:12 +0100 Subject: [PATCH 034/147] Simplify fetching of `DrawableChannel` --- osu.Game.Tests/Visual/Online/TestSceneChatOverlayV2.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneChatOverlayV2.cs b/osu.Game.Tests/Visual/Online/TestSceneChatOverlayV2.cs index 7c77ac925e..259f5540b7 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneChatOverlayV2.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneChatOverlayV2.cs @@ -365,7 +365,7 @@ namespace osu.Game.Tests.Visual.Online AddAssert("TextBox is focused", () => InputManager.FocusedDrawable == chatOverlayTextBox); AddStep("Click listing", () => clickDrawable(chatOverlay.ChildrenOfType().Single())); AddAssert("TextBox is focused", () => InputManager.FocusedDrawable == chatOverlayTextBox); - AddStep("Click drawable channel", () => clickDrawable(chatOverlay.ChildrenOfType().Single())); + AddStep("Click drawable channel", () => clickDrawable(currentDrawableChannel)); AddAssert("TextBox is focused", () => InputManager.FocusedDrawable == chatOverlayTextBox); AddStep("Click channel list", () => clickDrawable(chatOverlay.ChildrenOfType().Single())); AddAssert("TextBox is focused", () => InputManager.FocusedDrawable == chatOverlayTextBox); @@ -382,7 +382,7 @@ namespace osu.Game.Tests.Visual.Online chatOverlay.ChildrenOfType().Single().State.Value; private DrawableChannel currentDrawableChannel => - chatOverlay.ChildrenOfType>().Single().Child; + chatOverlay.ChildrenOfType().Single(); private ChannelListItem getChannelListItem(Channel channel) => chatOverlay.ChildrenOfType().Single(item => item.Channel == channel); From 9ec8b609a8395e1f814604b2c1f1c097f7a90d5b Mon Sep 17 00:00:00 2001 From: Jai Sharma Date: Mon, 9 May 2022 12:00:49 +0100 Subject: [PATCH 035/147] Update channel visibility tests to be more correct --- .../Visual/Online/TestSceneChatOverlayV2.cs | 42 ++++++++++++------- 1 file changed, 28 insertions(+), 14 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneChatOverlayV2.cs b/osu.Game.Tests/Visual/Online/TestSceneChatOverlayV2.cs index 259f5540b7..9956b8977f 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneChatOverlayV2.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneChatOverlayV2.cs @@ -121,6 +121,19 @@ namespace osu.Game.Tests.Visual.Online }); } + [Test] + public void TestBasic() + { + AddStep("Show overlay with channel", () => + { + chatOverlay.Show(); + Channel joinedChannel = channelManager.JoinChannel(testChannel1); + channelManager.CurrentChannel.Value = joinedChannel; + }); + AddAssert("Overlay is visible", () => chatOverlay.State.Value == Visibility.Visible); + AddAssert("Channel is visible", () => channelIsVisible && currentDrawableChannel.Channel == testChannel1); + } + [Test] public void TestShowHide() { @@ -158,20 +171,17 @@ namespace osu.Game.Tests.Visual.Online public void TestChannelSelection() { AddStep("Show overlay", () => chatOverlay.Show()); - AddAssert("Listing is visible", () => listingVisibility == Visibility.Visible); + AddAssert("Listing is visible", () => listingIsVisible); AddStep("Join channel 1", () => channelManager.JoinChannel(testChannel1)); AddStep("Select channel 1", () => clickDrawable(getChannelListItem(testChannel1))); - AddAssert("Listing is hidden", () => listingVisibility == Visibility.Hidden); - AddAssert("Loading is hidden", () => loadingVisibility == Visibility.Hidden); - AddAssert("Current channel is correct", () => channelManager.CurrentChannel.Value == testChannel1); - AddAssert("DrawableChannel is correct", () => currentDrawableChannel.Channel == testChannel1); + AddAssert("Channel 1 is visible", () => channelIsVisible && currentDrawableChannel.Channel == testChannel1); } [Test] public void TestSearchInListing() { AddStep("Show overlay", () => chatOverlay.Show()); - AddAssert("Listing is visible", () => listingVisibility == Visibility.Visible); + AddAssert("Listing is visible", () => listingIsVisible); AddStep("Search for 'number 2'", () => chatOverlayTextBox.Text = "number 2"); AddUntilStep("Only channel 2 visibile", () => { @@ -263,6 +273,7 @@ namespace osu.Game.Tests.Visual.Online }); }); AddStep("Highlight message", () => chatOverlay.HighlightMessage(message, testChannel1)); + AddAssert("Channel 1 is visible", () => channelIsVisible && currentDrawableChannel.Channel == testChannel1); } [Test] @@ -285,8 +296,7 @@ namespace osu.Game.Tests.Visual.Online }); }); AddStep("Highlight message", () => chatOverlay.HighlightMessage(message, testChannel2)); - AddAssert("Channel 2 is selected", () => channelManager.CurrentChannel.Value == testChannel2); - AddAssert("Channel 2 is visible", () => currentDrawableChannel.Channel == testChannel2); + AddAssert("Channel 2 is visible", () => channelIsVisible && currentDrawableChannel.Channel == testChannel2); } [Test] @@ -310,8 +320,7 @@ namespace osu.Game.Tests.Visual.Online }); AddStep("Leave channel 2", () => channelManager.LeaveChannel(testChannel2)); AddStep("Highlight message", () => chatOverlay.HighlightMessage(message, testChannel2)); - AddAssert("Channel 2 is selected", () => channelManager.CurrentChannel.Value == testChannel2); - AddAssert("Channel 2 is visible", () => currentDrawableChannel.Channel == testChannel2); + AddAssert("Channel 2 is visible", () => channelIsVisible && currentDrawableChannel.Channel == testChannel2); } [Test] @@ -331,6 +340,7 @@ namespace osu.Game.Tests.Visual.Online }); }); AddStep("Highlight message", () => chatOverlay.HighlightMessage(message, testChannel1)); + AddAssert("Channel 1 is visible", () => channelIsVisible && currentDrawableChannel.Channel == testChannel1); } [Test] @@ -351,6 +361,7 @@ namespace osu.Game.Tests.Visual.Online }); AddStep("Set null channel", () => channelManager.CurrentChannel.Value = null); AddStep("Highlight message", () => chatOverlay.HighlightMessage(message, testChannel1)); + AddAssert("Channel 1 is visible", () => channelIsVisible && currentDrawableChannel.Channel == testChannel1); } [Test] @@ -375,11 +386,14 @@ namespace osu.Game.Tests.Visual.Online AddAssert("TextBox is not focused", () => InputManager.FocusedDrawable == null); } - private Visibility listingVisibility => - chatOverlay.ChildrenOfType().Single().State.Value; + private bool listingIsVisible => + chatOverlay.ChildrenOfType().Single().State.Value == Visibility.Visible; - private Visibility loadingVisibility => - chatOverlay.ChildrenOfType().Single().State.Value; + private bool loadingIsVisible => + chatOverlay.ChildrenOfType().Single().State.Value == Visibility.Visible; + + private bool channelIsVisible => + !listingIsVisible && !loadingIsVisible; private DrawableChannel currentDrawableChannel => chatOverlay.ChildrenOfType().Single(); From d4cc2bd7dd813733c927b69999ef8bcb801450cc Mon Sep 17 00:00:00 2001 From: Jai Sharma Date: Mon, 9 May 2022 12:01:30 +0100 Subject: [PATCH 036/147] Ensure channel selector is dismissed when the current channel is changed --- osu.Game/Overlays/ChatOverlayV2.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/ChatOverlayV2.cs b/osu.Game/Overlays/ChatOverlayV2.cs index 4c7fa0f802..48b34726f8 100644 --- a/osu.Game/Overlays/ChatOverlayV2.cs +++ b/osu.Game/Overlays/ChatOverlayV2.cs @@ -191,8 +191,6 @@ namespace osu.Game.Overlays channelManager.CurrentChannel.Value = channel; } - selectorActive.Value = false; - channel.HighlightedMessage.Value = message; Show(); @@ -268,6 +266,8 @@ namespace osu.Game.Overlays return; } + selectorActive.Value = false; + LoadComponentAsync(new DrawableChannel(newChannel), loaded => { currentChannelContainer.Clear(); From ceb6276d2ffd3e0e9b900225ea28bd12efdca146 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 9 May 2022 18:19:29 +0300 Subject: [PATCH 037/147] Add failing test case --- .../UserInterface/TestSceneRoundedButton.cs | 48 ++++++++++++++++--- 1 file changed, 41 insertions(+), 7 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneRoundedButton.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneRoundedButton.cs index 9ccfba7c74..ef3c0c7fa4 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneRoundedButton.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneRoundedButton.cs @@ -2,11 +2,16 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Collections.Generic; +using System.Linq; using NUnit.Framework; +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.Graphics.UserInterfaceV2; +using osu.Game.Overlays; +using osuTK; namespace osu.Game.Tests.Visual.UserInterface { @@ -22,15 +27,10 @@ namespace osu.Game.Tests.Visual.UserInterface RelativeSizeAxes = Axes.Both, Children = new Drawable[] { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = Colour4.DarkGray - }, button = new RoundedButton { Width = 400, - Text = "Test button", + Text = "Test Button", Anchor = Anchor.Centre, Origin = Anchor.Centre, Action = () => { } @@ -40,5 +40,39 @@ namespace osu.Game.Tests.Visual.UserInterface AddToggleStep("toggle disabled", disabled => button.Action = disabled ? (Action)null : () => { }); } + + [Test] + public void TestOverlay() + { + IEnumerable schemes = Enum.GetValues(typeof(OverlayColourScheme)).Cast(); + + AddStep("create buttons", () => + { + Child = new FillFlowContainer + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Vertical, + Spacing = new Vector2(5f), + ChildrenEnumerable = schemes.Select(c => new DependencyProvidingContainer + { + AutoSizeAxes = Axes.Both, + CachedDependencies = new (Type, object)[] { (typeof(OverlayColourProvider), new OverlayColourProvider(c)) }, + Child = new RoundedButton + { + Width = 400, + Text = $"Test {c}", + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Action = () => { }, + } + }), + }; + }); + + AddAssert("first button has correct colour", () => this.ChildrenOfType().First().BackgroundColour == new OverlayColourProvider(schemes.First()).Highlight1); + AddToggleStep("toggle disabled", disabled => this.ChildrenOfType().ForEach(b => b.Action = disabled ? (Action)null : () => { })); + } } } From 172524ff8a3812ffce89361c5552a6426529df33 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 9 May 2022 18:21:00 +0300 Subject: [PATCH 038/147] Move default background colour specification to `OsuButton` --- osu.Game/Graphics/UserInterface/OsuButton.cs | 8 +++++--- osu.Game/Graphics/UserInterfaceV2/RoundedButton.cs | 11 ----------- 2 files changed, 5 insertions(+), 14 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/OsuButton.cs b/osu.Game/Graphics/UserInterface/OsuButton.cs index 29a797bd78..6fa63e5874 100644 --- a/osu.Game/Graphics/UserInterface/OsuButton.cs +++ b/osu.Game/Graphics/UserInterface/OsuButton.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System.Diagnostics; +using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; @@ -12,6 +13,7 @@ using osu.Framework.Graphics.UserInterface; using osu.Framework.Input.Events; using osu.Framework.Localisation; using osu.Game.Graphics.Sprites; +using osu.Game.Overlays; using osuTK.Graphics; namespace osu.Game.Graphics.UserInterface @@ -86,11 +88,11 @@ namespace osu.Game.Graphics.UserInterface AddInternal(new HoverClickSounds(hoverSounds.Value)); } - [BackgroundDependencyLoader] - private void load(OsuColour colours) + [BackgroundDependencyLoader(permitNulls: true)] + private void load([CanBeNull] OverlayColourProvider overlayColourProvider, OsuColour colours) { if (backgroundColour == null) - BackgroundColour = colours.BlueDark; + BackgroundColour = overlayColourProvider?.Highlight1 ?? colours.Blue3; } protected override void LoadComplete() diff --git a/osu.Game/Graphics/UserInterfaceV2/RoundedButton.cs b/osu.Game/Graphics/UserInterfaceV2/RoundedButton.cs index f535a32b39..b1529774d9 100644 --- a/osu.Game/Graphics/UserInterfaceV2/RoundedButton.cs +++ b/osu.Game/Graphics/UserInterfaceV2/RoundedButton.cs @@ -2,13 +2,9 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; -using JetBrains.Annotations; -using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Graphics.UserInterface; -using osu.Game.Overlays; -using osuTK.Graphics; namespace osu.Game.Graphics.UserInterfaceV2 { @@ -26,13 +22,6 @@ namespace osu.Game.Graphics.UserInterfaceV2 } } - [BackgroundDependencyLoader(true)] - private void load([CanBeNull] OverlayColourProvider overlayColourProvider, OsuColour colours) - { - if (BackgroundColour == Color4.White) - BackgroundColour = overlayColourProvider?.Highlight1 ?? colours.Blue3; - } - protected override void LoadComplete() { base.LoadComplete(); From ace25af949ab39946e4a8b57103b40247c96dbfa Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 9 May 2022 20:55:26 +0300 Subject: [PATCH 039/147] Revert "Move default background colour specification to `OsuButton`" This reverts commit 172524ff8a3812ffce89361c5552a6426529df33. --- osu.Game/Graphics/UserInterface/OsuButton.cs | 8 +++----- osu.Game/Graphics/UserInterfaceV2/RoundedButton.cs | 11 +++++++++++ 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/OsuButton.cs b/osu.Game/Graphics/UserInterface/OsuButton.cs index 6fa63e5874..29a797bd78 100644 --- a/osu.Game/Graphics/UserInterface/OsuButton.cs +++ b/osu.Game/Graphics/UserInterface/OsuButton.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using System.Diagnostics; -using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; @@ -13,7 +12,6 @@ using osu.Framework.Graphics.UserInterface; using osu.Framework.Input.Events; using osu.Framework.Localisation; using osu.Game.Graphics.Sprites; -using osu.Game.Overlays; using osuTK.Graphics; namespace osu.Game.Graphics.UserInterface @@ -88,11 +86,11 @@ namespace osu.Game.Graphics.UserInterface AddInternal(new HoverClickSounds(hoverSounds.Value)); } - [BackgroundDependencyLoader(permitNulls: true)] - private void load([CanBeNull] OverlayColourProvider overlayColourProvider, OsuColour colours) + [BackgroundDependencyLoader] + private void load(OsuColour colours) { if (backgroundColour == null) - BackgroundColour = overlayColourProvider?.Highlight1 ?? colours.Blue3; + BackgroundColour = colours.BlueDark; } protected override void LoadComplete() diff --git a/osu.Game/Graphics/UserInterfaceV2/RoundedButton.cs b/osu.Game/Graphics/UserInterfaceV2/RoundedButton.cs index b1529774d9..f535a32b39 100644 --- a/osu.Game/Graphics/UserInterfaceV2/RoundedButton.cs +++ b/osu.Game/Graphics/UserInterfaceV2/RoundedButton.cs @@ -2,9 +2,13 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; +using JetBrains.Annotations; +using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Graphics.UserInterface; +using osu.Game.Overlays; +using osuTK.Graphics; namespace osu.Game.Graphics.UserInterfaceV2 { @@ -22,6 +26,13 @@ namespace osu.Game.Graphics.UserInterfaceV2 } } + [BackgroundDependencyLoader(true)] + private void load([CanBeNull] OverlayColourProvider overlayColourProvider, OsuColour colours) + { + if (BackgroundColour == Color4.White) + BackgroundColour = overlayColourProvider?.Highlight1 ?? colours.Blue3; + } + protected override void LoadComplete() { base.LoadComplete(); From 1fcfeac05f8c499ae86c36d32bcfdaf0bfc736f4 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 9 May 2022 18:52:09 +0300 Subject: [PATCH 040/147] Fix `RoundedButton` not using its default background colour --- osu.Game/Graphics/UserInterface/OsuButton.cs | 31 ++++++++++++++----- .../Graphics/UserInterfaceV2/RoundedButton.cs | 4 +-- 2 files changed, 24 insertions(+), 11 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/OsuButton.cs b/osu.Game/Graphics/UserInterface/OsuButton.cs index 29a797bd78..08514d94c3 100644 --- a/osu.Game/Graphics/UserInterface/OsuButton.cs +++ b/osu.Game/Graphics/UserInterface/OsuButton.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.Diagnostics; using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; @@ -33,9 +32,12 @@ namespace osu.Game.Graphics.UserInterface private Color4? backgroundColour; + /// + /// Sets a custom background colour to this button, replacing the provided default. + /// public Color4 BackgroundColour { - get => backgroundColour ?? Color4.White; + get => backgroundColour ?? defaultBackgroundColour; set { backgroundColour = value; @@ -43,6 +45,23 @@ namespace osu.Game.Graphics.UserInterface } } + private Color4 defaultBackgroundColour; + + /// + /// Sets a default background colour to this button. + /// + protected Color4 DefaultBackgroundColour + { + get => defaultBackgroundColour; + set + { + defaultBackgroundColour = value; + + if (backgroundColour == null) + Background.FadeColour(value); + } + } + protected override Container Content { get; } protected Box Hover; @@ -89,8 +108,7 @@ namespace osu.Game.Graphics.UserInterface [BackgroundDependencyLoader] private void load(OsuColour colours) { - if (backgroundColour == null) - BackgroundColour = colours.BlueDark; + DefaultBackgroundColour = colours.BlueDark; } protected override void LoadComplete() @@ -106,10 +124,7 @@ namespace osu.Game.Graphics.UserInterface protected override bool OnClick(ClickEvent e) { if (Enabled.Value) - { - Debug.Assert(backgroundColour != null); - Background.FlashColour(backgroundColour.Value.Lighten(0.4f), 200); - } + Background.FlashColour(BackgroundColour.Lighten(0.4f), 200); return base.OnClick(e); } diff --git a/osu.Game/Graphics/UserInterfaceV2/RoundedButton.cs b/osu.Game/Graphics/UserInterfaceV2/RoundedButton.cs index f535a32b39..ec56b6d784 100644 --- a/osu.Game/Graphics/UserInterfaceV2/RoundedButton.cs +++ b/osu.Game/Graphics/UserInterfaceV2/RoundedButton.cs @@ -8,7 +8,6 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Graphics.UserInterface; using osu.Game.Overlays; -using osuTK.Graphics; namespace osu.Game.Graphics.UserInterfaceV2 { @@ -29,8 +28,7 @@ namespace osu.Game.Graphics.UserInterfaceV2 [BackgroundDependencyLoader(true)] private void load([CanBeNull] OverlayColourProvider overlayColourProvider, OsuColour colours) { - if (BackgroundColour == Color4.White) - BackgroundColour = overlayColourProvider?.Highlight1 ?? colours.Blue3; + DefaultBackgroundColour = overlayColourProvider?.Highlight1 ?? colours.Blue3; } protected override void LoadComplete() From 5726cf660f7ec219804fb0ebb6315ab5af5017ed Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 9 May 2022 21:19:34 +0300 Subject: [PATCH 041/147] Improve test coverage to use existing `ThemeComparisonTestScene` --- .../UserInterface/TestSceneRoundedButton.cs | 74 +++++-------------- 1 file changed, 18 insertions(+), 56 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneRoundedButton.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneRoundedButton.cs index ef3c0c7fa4..f45c55d912 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneRoundedButton.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneRoundedButton.cs @@ -1,78 +1,40 @@ // 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 NUnit.Framework; -using osu.Framework.Extensions.IEnumerableExtensions; +using osu.Framework.Bindables; using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; using osu.Framework.Testing; using osu.Game.Graphics.UserInterfaceV2; using osu.Game.Overlays; -using osuTK; namespace osu.Game.Tests.Visual.UserInterface { - public class TestSceneRoundedButton : OsuTestScene + public class TestSceneRoundedButton : ThemeComparisonTestScene { - [Test] - public void TestBasic() + private readonly BindableBool enabled = new BindableBool(true); + + protected override Drawable CreateContent() => new RoundedButton { - RoundedButton button = null; + Width = 400, + Text = "Test button", + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Enabled = { BindTarget = enabled }, + }; - AddStep("create button", () => Child = new Container - { - RelativeSizeAxes = Axes.Both, - Children = new Drawable[] - { - button = new RoundedButton - { - Width = 400, - Text = "Test Button", - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Action = () => { } - } - } - }); - - AddToggleStep("toggle disabled", disabled => button.Action = disabled ? (Action)null : () => { }); + [Test] + public void TestDisabled() + { + AddToggleStep("toggle disabled", disabled => enabled.Value = !disabled); } [Test] - public void TestOverlay() + public void TestBackgroundColour() { - IEnumerable schemes = Enum.GetValues(typeof(OverlayColourScheme)).Cast(); - - AddStep("create buttons", () => - { - Child = new FillFlowContainer - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Vertical, - Spacing = new Vector2(5f), - ChildrenEnumerable = schemes.Select(c => new DependencyProvidingContainer - { - AutoSizeAxes = Axes.Both, - CachedDependencies = new (Type, object)[] { (typeof(OverlayColourProvider), new OverlayColourProvider(c)) }, - Child = new RoundedButton - { - Width = 400, - Text = $"Test {c}", - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Action = () => { }, - } - }), - }; - }); - - AddAssert("first button has correct colour", () => this.ChildrenOfType().First().BackgroundColour == new OverlayColourProvider(schemes.First()).Highlight1); - AddToggleStep("toggle disabled", disabled => this.ChildrenOfType().ForEach(b => b.Action = disabled ? (Action)null : () => { })); + AddStep("set red scheme", () => CreateThemedContent(OverlayColourScheme.Red)); + AddAssert("first button has correct colour", () => Cell(0, 1).ChildrenOfType().First().BackgroundColour == new OverlayColourProvider(OverlayColourScheme.Red).Highlight1); } } } From ebb64d1f1ab7fb2b398ba11803690f0e83d56651 Mon Sep 17 00:00:00 2001 From: Jai Sharma Date: Mon, 9 May 2022 20:15:43 +0100 Subject: [PATCH 042/147] Use `AddUntilStep` to wait for channel to load and become visible --- .../Visual/Online/TestSceneChatOverlayV2.cs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneChatOverlayV2.cs b/osu.Game.Tests/Visual/Online/TestSceneChatOverlayV2.cs index 9956b8977f..bf1767cc96 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneChatOverlayV2.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneChatOverlayV2.cs @@ -131,7 +131,7 @@ namespace osu.Game.Tests.Visual.Online channelManager.CurrentChannel.Value = joinedChannel; }); AddAssert("Overlay is visible", () => chatOverlay.State.Value == Visibility.Visible); - AddAssert("Channel is visible", () => channelIsVisible && currentDrawableChannel.Channel == testChannel1); + AddUntilStep("Channel is visible", () => channelIsVisible && currentDrawableChannel.Channel == testChannel1); } [Test] @@ -174,7 +174,7 @@ namespace osu.Game.Tests.Visual.Online AddAssert("Listing is visible", () => listingIsVisible); AddStep("Join channel 1", () => channelManager.JoinChannel(testChannel1)); AddStep("Select channel 1", () => clickDrawable(getChannelListItem(testChannel1))); - AddAssert("Channel 1 is visible", () => channelIsVisible && currentDrawableChannel.Channel == testChannel1); + AddUntilStep("Channel 1 is visible", () => channelIsVisible && currentDrawableChannel.Channel == testChannel1); } [Test] @@ -273,7 +273,7 @@ namespace osu.Game.Tests.Visual.Online }); }); AddStep("Highlight message", () => chatOverlay.HighlightMessage(message, testChannel1)); - AddAssert("Channel 1 is visible", () => channelIsVisible && currentDrawableChannel.Channel == testChannel1); + AddUntilStep("Channel 1 is visible", () => channelIsVisible && currentDrawableChannel.Channel == testChannel1); } [Test] @@ -296,7 +296,7 @@ namespace osu.Game.Tests.Visual.Online }); }); AddStep("Highlight message", () => chatOverlay.HighlightMessage(message, testChannel2)); - AddAssert("Channel 2 is visible", () => channelIsVisible && currentDrawableChannel.Channel == testChannel2); + AddUntilStep("Channel 2 is visible", () => channelIsVisible && currentDrawableChannel.Channel == testChannel2); } [Test] @@ -320,7 +320,7 @@ namespace osu.Game.Tests.Visual.Online }); AddStep("Leave channel 2", () => channelManager.LeaveChannel(testChannel2)); AddStep("Highlight message", () => chatOverlay.HighlightMessage(message, testChannel2)); - AddAssert("Channel 2 is visible", () => channelIsVisible && currentDrawableChannel.Channel == testChannel2); + AddUntilStep("Channel 2 is visible", () => channelIsVisible && currentDrawableChannel.Channel == testChannel2); } [Test] @@ -340,7 +340,7 @@ namespace osu.Game.Tests.Visual.Online }); }); AddStep("Highlight message", () => chatOverlay.HighlightMessage(message, testChannel1)); - AddAssert("Channel 1 is visible", () => channelIsVisible && currentDrawableChannel.Channel == testChannel1); + AddUntilStep("Channel 1 is visible", () => channelIsVisible && currentDrawableChannel.Channel == testChannel1); } [Test] @@ -361,7 +361,7 @@ namespace osu.Game.Tests.Visual.Online }); AddStep("Set null channel", () => channelManager.CurrentChannel.Value = null); AddStep("Highlight message", () => chatOverlay.HighlightMessage(message, testChannel1)); - AddAssert("Channel 1 is visible", () => channelIsVisible && currentDrawableChannel.Channel == testChannel1); + AddUntilStep("Channel 1 is visible", () => channelIsVisible && currentDrawableChannel.Channel == testChannel1); } [Test] From 38e463d31da24d4721efc22e0541505831268c36 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 9 May 2022 22:16:04 +0200 Subject: [PATCH 043/147] Add failing test case for invalid mod adjustment management --- .../UserInterface/TestSceneModSelectScreen.cs | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectScreen.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectScreen.cs index fa7758df59..661465b484 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectScreen.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectScreen.cs @@ -481,6 +481,38 @@ namespace osu.Game.Tests.Visual.UserInterface AddUntilStep("3 columns visible", () => this.ChildrenOfType().Count(col => col.IsPresent) == 3); } + [Test] + public void TestCorrectAudioAdjustmentDeapplication() + { + createScreen(); + changeRuleset(0); + + AddStep("allow track adjustments", () => MusicController.AllowTrackAdjustments = true); + + AddStep("set wind up", () => modSelectScreen.SelectedMods.Value = new[] { new ModWindUp() }); + AddStep("open customisation menu", () => + { + InputManager.MoveMouseTo(this.ChildrenOfType().Single()); + InputManager.Click(MouseButton.Left); + }); + AddAssert("frequency above 1", () => MusicController.CurrentTrack.AggregateFrequency.Value > 1); + AddAssert("tempo is 1", () => MusicController.CurrentTrack.AggregateTempo.Value == 1); + + AddStep("turn off pitch adjustment", () => + { + InputManager.MoveMouseTo(this.ChildrenOfType().Single()); + InputManager.Click(MouseButton.Left); + }); + AddAssert("frequency is 1", () => MusicController.CurrentTrack.AggregateFrequency.Value == 1); + AddAssert("tempo above 1", () => MusicController.CurrentTrack.AggregateTempo.Value > 1); + + AddStep("reset mods", () => modSelectScreen.SelectedMods.SetDefault()); + AddAssert("frequency is 1", () => MusicController.CurrentTrack.AggregateFrequency.Value == 1); + AddAssert("tempo is 1", () => MusicController.CurrentTrack.AggregateTempo.Value == 1); + + AddStep("disallow track adjustments", () => MusicController.AllowTrackAdjustments = false); + } + private void waitForColumnLoad() => AddUntilStep("all column content loaded", () => modSelectScreen.ChildrenOfType().Any() && modSelectScreen.ChildrenOfType().All(column => column.IsLoaded && column.ItemsLoaded)); From 96ea4ee7b33136a4fb563520c730c971eaf63026 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 9 May 2022 23:49:05 +0300 Subject: [PATCH 044/147] Add explanatory comment --- osu.Game/Overlays/SettingsToolboxGroup.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Overlays/SettingsToolboxGroup.cs b/osu.Game/Overlays/SettingsToolboxGroup.cs index 36d4f03e02..077762c0d0 100644 --- a/osu.Game/Overlays/SettingsToolboxGroup.cs +++ b/osu.Game/Overlays/SettingsToolboxGroup.cs @@ -156,6 +156,8 @@ namespace osu.Game.Overlays private void updateExpandedState(ValueChangedEvent expanded) { + // clearing transforms is necessary to avoid a previous height transform + // potentially continuing to get processed while content has changed to autosize. content.ClearTransforms(); if (expanded.NewValue) From beb86a7f7c909f27ba0f8fa217338e32a067bac4 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 9 May 2022 23:57:08 +0300 Subject: [PATCH 045/147] Remove usage of player settings in `TestSceneSettingsToolboxGroup` --- .../TestSceneSettingsToolboxGroup.cs | 73 ++++++++----------- 1 file changed, 29 insertions(+), 44 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneSettingsToolboxGroup.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneSettingsToolboxGroup.cs index 76657afbcd..8ef24e58a0 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneSettingsToolboxGroup.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneSettingsToolboxGroup.cs @@ -4,12 +4,11 @@ using System.Linq; using NUnit.Framework; using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; using osu.Framework.Testing; using osu.Game.Graphics.UserInterface; +using osu.Game.Graphics.UserInterfaceV2; using osu.Game.Overlays; -using osu.Game.Screens.Play.HUD; -using osu.Game.Screens.Play.PlayerSettings; +using osu.Game.Overlays.Settings; using osuTK.Input; namespace osu.Game.Tests.Visual.UserInterface @@ -17,45 +16,39 @@ namespace osu.Game.Tests.Visual.UserInterface [TestFixture] public class TestSceneSettingsToolboxGroup : OsuManualInputManagerTestScene { - public TestSceneSettingsToolboxGroup() + private SettingsToolboxGroup group; + + [SetUp] + public void SetUp() => Schedule(() => { - ExampleContainer container; - - Add(new PlayerSettingsOverlay + Child = group = new SettingsToolboxGroup("example") { - Anchor = Anchor.TopRight, - Origin = Anchor.TopRight, - State = { Value = Visibility.Visible } - }); - - Add(container = new ExampleContainer()); - - AddStep(@"Add button", () => container.Add(new TriangleButton - { - RelativeSizeAxes = Axes.X, - Text = @"Button", - })); - - AddStep(@"Add checkbox", () => container.Add(new PlayerCheckbox - { - LabelText = "Checkbox", - })); - - AddStep(@"Add textbox", () => container.Add(new FocusedTextBox - { - RelativeSizeAxes = Axes.X, - Height = 30, - PlaceholderText = "Textbox", - HoldFocus = false, - })); - } + Children = new Drawable[] + { + new RoundedButton + { + RelativeSizeAxes = Axes.X, + Text = @"Button", + Enabled = { Value = true }, + }, + new OsuCheckbox + { + LabelText = @"Checkbox", + }, + new OutlinedTextBox + { + RelativeSizeAxes = Axes.X, + Height = 30, + PlaceholderText = @"Textbox", + } + }, + }; + }); [Test] public void TestClickExpandButtonMultipleTimes() { - SettingsToolboxGroup group = null; - - AddAssert("group expanded by default", () => (group = this.ChildrenOfType().First()).Expanded.Value); + AddAssert("group expanded by default", () => group.Expanded.Value); AddStep("click expand button multiple times", () => { InputManager.MoveMouseTo(group.ChildrenOfType().Single()); @@ -65,13 +58,5 @@ namespace osu.Game.Tests.Visual.UserInterface }); AddAssert("group contracted", () => !group.Expanded.Value); } - - private class ExampleContainer : PlayerSettingsGroup - { - public ExampleContainer() - : base("example") - { - } - } } } From 2481201a734ef248e2f99f58734f136389b93393 Mon Sep 17 00:00:00 2001 From: Jai Sharma Date: Mon, 9 May 2022 22:58:46 +0100 Subject: [PATCH 046/147] Fix selector dismissal behaviour during user actions --- osu.Game/Overlays/ChatOverlayV2.cs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/ChatOverlayV2.cs b/osu.Game/Overlays/ChatOverlayV2.cs index 48b34726f8..e59bee7977 100644 --- a/osu.Game/Overlays/ChatOverlayV2.cs +++ b/osu.Game/Overlays/ChatOverlayV2.cs @@ -165,7 +165,12 @@ namespace osu.Game.Overlays }; channelList.OnRequestLeave += channel => channelManager.LeaveChannel(channel); - channelListing.OnRequestJoin += channel => channelManager.JoinChannel(channel); + channelListing.OnRequestJoin += channel => + { + channelManager.JoinChannel(channel); + // Manually joining a channel should keep the selector open + selectorActive.Value = true; + }; channelListing.OnRequestLeave += channel => channelManager.LeaveChannel(channel); textBar.OnSearchTermsChanged += searchTerms => channelListing.SearchTerm = searchTerms; @@ -255,6 +260,10 @@ namespace osu.Game.Overlays // Channel is null when leaving the currently selected channel if (newChannel == null) { + // Don't need to autoswitch if the selector is visible + if (selectorActive.Value) + return; + // Find another channel to switch to newChannel = channelManager.JoinedChannels.FirstOrDefault(c => c != channel.OldValue); From 2b7eeadac0fbf52419050fbd8e4fe69ff93de893 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 10 May 2022 14:48:41 +0900 Subject: [PATCH 047/147] Workaround bad performance when selecting all freemods --- osu.Game/Overlays/Mods/ModSelectScreen.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Mods/ModSelectScreen.cs b/osu.Game/Overlays/Mods/ModSelectScreen.cs index 16975cfd1d..1c629b7a4f 100644 --- a/osu.Game/Overlays/Mods/ModSelectScreen.cs +++ b/osu.Game/Overlays/Mods/ModSelectScreen.cs @@ -192,7 +192,8 @@ namespace osu.Game.Overlays.Mods State.BindValueChanged(_ => samplePlaybackDisabled.Value = State.Value == Visibility.Hidden, true); - ((IBindable>)modSettingsArea.SelectedMods).BindTo(SelectedMods); + if (customisationButton != null) + ((IBindable>)modSettingsArea.SelectedMods).BindTo(SelectedMods); SelectedMods.BindValueChanged(val => { From 8ccf2ee0759e58e14d8544c32297e410a8c5fb5e Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 10 May 2022 15:07:08 +0900 Subject: [PATCH 048/147] Add inline comment --- osu.Game/Overlays/Mods/ModSelectScreen.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Overlays/Mods/ModSelectScreen.cs b/osu.Game/Overlays/Mods/ModSelectScreen.cs index 1c629b7a4f..912c09f05c 100644 --- a/osu.Game/Overlays/Mods/ModSelectScreen.cs +++ b/osu.Game/Overlays/Mods/ModSelectScreen.cs @@ -192,6 +192,8 @@ namespace osu.Game.Overlays.Mods State.BindValueChanged(_ => samplePlaybackDisabled.Value = State.Value == Visibility.Hidden, true); + // This is an optimisation to prevent refreshing the available settings controls when it can be + // reasonably assumed that the settings panel is never to be displayed (e.g. FreeModSelectScreen). if (customisationButton != null) ((IBindable>)modSettingsArea.SelectedMods).BindTo(SelectedMods); From bcd91ac743885c1bce1df4bf6a84de925004b73d Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 10 May 2022 10:02:32 +0300 Subject: [PATCH 049/147] Move exception soft-handling logic to `OsuGameBase` --- osu.Desktop/Program.cs | 22 ---------------------- osu.Game/OsuGameBase.cs | 30 ++++++++++++++++++++++++++++++ 2 files changed, 30 insertions(+), 22 deletions(-) diff --git a/osu.Desktop/Program.cs b/osu.Desktop/Program.cs index eb9045d9ce..405f0a8006 100644 --- a/osu.Desktop/Program.cs +++ b/osu.Desktop/Program.cs @@ -4,8 +4,6 @@ using System; using System.IO; using System.Runtime.Versioning; -using System.Threading; -using System.Threading.Tasks; using osu.Desktop.LegacyIpc; using osu.Framework; using osu.Framework.Development; @@ -63,8 +61,6 @@ namespace osu.Desktop using (DesktopGameHost host = Host.GetSuitableDesktopHost(gameName, new HostOptions { BindIPC = true })) { - host.ExceptionThrown += handleException; - if (!host.IsPrimaryInstance) { if (args.Length > 0 && args[0].Contains('.')) // easy way to check for a file import in args @@ -131,23 +127,5 @@ namespace osu.Desktop // tools.SetProcessAppUserModelId(); }); } - - private static int allowableExceptions = DebugUtils.IsDebugBuild ? 0 : 1; - - /// - /// Allow a maximum of one unhandled exception, per second of execution. - /// - /// - private static bool handleException(Exception arg) - { - bool continueExecution = Interlocked.Decrement(ref allowableExceptions) >= 0; - - Logger.Log($"Unhandled exception has been {(continueExecution ? $"allowed with {allowableExceptions} more allowable exceptions" : "denied")} ."); - - // restore the stock of allowable exceptions after a short delay. - Task.Delay(1000).ContinueWith(_ => Interlocked.Increment(ref allowableExceptions)); - - return continueExecution; - } } } diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 324fcada89..c5b69a3637 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -7,6 +7,7 @@ using System.IO; using System.Linq; using System.Reflection; using System.Threading; +using System.Threading.Tasks; using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Bindables; @@ -180,9 +181,16 @@ namespace osu.Game /// protected DatabaseContextFactory EFContextFactory { get; private set; } + /// + /// Number of exceptions to allow before aborting execution. + /// + protected virtual int SoftHandledExceptions => 0; + public OsuGameBase() { Name = @"osu!"; + + allowableExceptions = SoftHandledExceptions; } [BackgroundDependencyLoader] @@ -408,6 +416,8 @@ namespace osu.Game LocalConfig ??= UseDevelopmentServer ? new DevelopmentOsuConfigManager(Storage) : new OsuConfigManager(Storage); + + host.ExceptionThrown += onExceptionThrown; } /// @@ -505,6 +515,23 @@ namespace osu.Game AvailableMods.Value = dict; } + private int allowableExceptions; + + /// + /// Allows a maximum of one unhandled exception, per second of execution. + /// + private bool onExceptionThrown(Exception _) + { + bool continueExecution = Interlocked.Decrement(ref allowableExceptions) >= 0; + + Logger.Log($"Unhandled exception has been {(continueExecution ? $"allowed with {SoftHandledExceptions} more allowable exceptions" : "denied")} ."); + + // restore the stock of allowable exceptions after a short delay. + Task.Delay(1000).ContinueWith(_ => Interlocked.Increment(ref allowableExceptions)); + + return continueExecution; + } + protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); @@ -514,6 +541,9 @@ namespace osu.Game LocalConfig?.Dispose(); realm?.Dispose(); + + if (Host != null) + Host.ExceptionThrown -= onExceptionThrown; } } } From 725f5f4dcb00616e5f7f65ab6595918ad2812a8a Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 10 May 2022 10:02:41 +0300 Subject: [PATCH 050/147] Allow one more exception per second in `OsuGame` and `TournamentGame` --- osu.Game.Tournament/TournamentGame.cs | 3 +++ osu.Game/OsuGame.cs | 3 +++ 2 files changed, 6 insertions(+) diff --git a/osu.Game.Tournament/TournamentGame.cs b/osu.Game.Tournament/TournamentGame.cs index 7967f54b49..d9db0e6d20 100644 --- a/osu.Game.Tournament/TournamentGame.cs +++ b/osu.Game.Tournament/TournamentGame.cs @@ -6,6 +6,7 @@ using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Configuration; +using osu.Framework.Development; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; @@ -39,6 +40,8 @@ namespace osu.Game.Tournament private Bindable windowMode; private LoadingSpinner loadingSpinner; + protected override int SoftHandledExceptions => DebugUtils.IsDebugBuild ? 0 : 1; + [BackgroundDependencyLoader] private void load(FrameworkConfigManager frameworkConfig, GameHost host) { diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 54c4231b06..6961dce910 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -13,6 +13,7 @@ using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Bindables; using osu.Framework.Configuration; +using osu.Framework.Development; using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -175,6 +176,8 @@ namespace osu.Game private readonly List visibleBlockingOverlays = new List(); + protected override int SoftHandledExceptions => DebugUtils.IsDebugBuild ? 0 : 1; + public OsuGame(string[] args = null) { this.args = args; From dc3c73f72300af57ad18b9671178c0bed2e1206d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 10 May 2022 14:03:03 +0900 Subject: [PATCH 051/147] Enable sentry session tracking --- osu.Game/Utils/SentryLogger.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Utils/SentryLogger.cs b/osu.Game/Utils/SentryLogger.cs index d9c8199f75..7e0449cec8 100644 --- a/osu.Game/Utils/SentryLogger.cs +++ b/osu.Game/Utils/SentryLogger.cs @@ -25,6 +25,8 @@ namespace osu.Game.Utils var options = new SentryOptions { Dsn = "https://ad9f78529cef40ac874afb95a9aca04e@sentry.ppy.sh/2", + AutoSessionTracking = true, + IsEnvironmentUser = false, Release = game.Version }; From 09c21cde8ca15a02ddd29dba669cb200fa9919fb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 10 May 2022 14:08:42 +0900 Subject: [PATCH 052/147] Add log level translation --- osu.Game/Utils/SentryLogger.cs | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/osu.Game/Utils/SentryLogger.cs b/osu.Game/Utils/SentryLogger.cs index 7e0449cec8..c39d718d62 100644 --- a/osu.Game/Utils/SentryLogger.cs +++ b/osu.Game/Utils/SentryLogger.cs @@ -50,12 +50,37 @@ namespace osu.Game.Utils if (lastException != null && lastException.Message == exception.Message && exception.StackTrace.StartsWith(lastException.StackTrace, StringComparison.Ordinal)) return; lastException = exception; - sentry.CaptureEvent(new SentryEvent(exception) { Message = entry.Message }, sentryScope); + sentry.CaptureEvent(new SentryEvent(exception) + { + Message = entry.Message, + Level = getSentryLevel(entry.Level), + }, sentryScope); } else sentryScope.AddBreadcrumb(DateTimeOffset.Now, entry.Message, entry.Target.ToString(), "navigation"); } + private SentryLevel? getSentryLevel(LogLevel entryLevel) + { + switch (entryLevel) + { + case LogLevel.Debug: + return SentryLevel.Debug; + + case LogLevel.Verbose: + return SentryLevel.Info; + + case LogLevel.Important: + return SentryLevel.Warning; + + case LogLevel.Error: + return SentryLevel.Error; + + default: + throw new ArgumentOutOfRangeException(nameof(entryLevel), entryLevel, null); + } + } + private bool shouldSubmitException(Exception exception) { switch (exception) From 64cc6ebddbb090edc7a39bb83415b68dc7f25ece Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 10 May 2022 14:12:31 +0900 Subject: [PATCH 053/147] Add local user tracking to sentry reporting --- osu.Game/OsuGameBase.cs | 2 +- osu.Game/Utils/SentryLogger.cs | 17 +++++++++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 324fcada89..3f0610e9aa 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -125,7 +125,7 @@ namespace osu.Game protected MusicController MusicController { get; private set; } - protected IAPIProvider API { get; set; } + protected internal IAPIProvider API { get; protected set; } protected Storage Storage { get; set; } diff --git a/osu.Game/Utils/SentryLogger.cs b/osu.Game/Utils/SentryLogger.cs index c39d718d62..5cecc4d776 100644 --- a/osu.Game/Utils/SentryLogger.cs +++ b/osu.Game/Utils/SentryLogger.cs @@ -4,7 +4,10 @@ using System; using System.IO; using System.Net; +using JetBrains.Annotations; +using osu.Framework.Bindables; using osu.Framework.Logging; +using osu.Game.Online.API.Requests.Responses; using Sentry; namespace osu.Game.Utils @@ -18,6 +21,9 @@ namespace osu.Game.Utils private Scope sentryScope; private Exception lastException; + [UsedImplicitly] + private readonly IBindable localUser; + public SentryLogger(OsuGame game) { if (!game.IsDeployedBuild) return; @@ -34,6 +40,16 @@ namespace osu.Game.Utils sentryScope = new Scope(options); Logger.NewEntry += processLogEntry; + + localUser = game.API.LocalUser.GetBoundCopy(); + localUser.BindValueChanged(user => + { + sentryScope.User = new User + { + Username = user.NewValue.Username, + Id = user.NewValue.Id.ToString(), + }; + }); } private void processLogEntry(LogEntry entry) @@ -50,6 +66,7 @@ namespace osu.Game.Utils if (lastException != null && lastException.Message == exception.Message && exception.StackTrace.StartsWith(lastException.StackTrace, StringComparison.Ordinal)) return; lastException = exception; + sentry.CaptureEvent(new SentryEvent(exception) { Message = entry.Message, From a5b454edc72537daaaf428bca8b34bf8a9c926b7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 10 May 2022 14:21:49 +0900 Subject: [PATCH 054/147] Remove unnecessary DI caching of `SentryLogger` --- osu.Game.Tests/Visual/Navigation/TestSceneOsuGame.cs | 2 -- osu.Game/OsuGame.cs | 2 -- 2 files changed, 4 deletions(-) diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneOsuGame.cs b/osu.Game.Tests/Visual/Navigation/TestSceneOsuGame.cs index 0f8337deb6..e4871f611e 100644 --- a/osu.Game.Tests/Visual/Navigation/TestSceneOsuGame.cs +++ b/osu.Game.Tests/Visual/Navigation/TestSceneOsuGame.cs @@ -23,7 +23,6 @@ using osu.Game.Rulesets.Mods; using osu.Game.Scoring; using osu.Game.Screens.Menu; using osu.Game.Skinning; -using osu.Game.Utils; namespace osu.Game.Tests.Visual.Navigation { @@ -33,7 +32,6 @@ namespace osu.Game.Tests.Visual.Navigation private IReadOnlyList requiredGameDependencies => new[] { typeof(OsuGame), - typeof(SentryLogger), typeof(OsuLogo), typeof(IdleTracker), typeof(OnScreenDisplay), diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 54c4231b06..a6c57998b0 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -258,8 +258,6 @@ namespace osu.Game { dependencies.CacheAs(this); - dependencies.Cache(SentryLogger); - dependencies.Cache(osuLogo = new OsuLogo { Alpha = 0 }); // bind config int to database RulesetInfo From 3338bffce3809d78d0f00e6b0b2bf332e62c8af7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 10 May 2022 14:25:10 +0900 Subject: [PATCH 055/147] Attach user to sentry later in startup flow --- osu.Game/OsuGame.cs | 2 ++ osu.Game/OsuGameBase.cs | 2 +- osu.Game/Utils/SentryLogger.cs | 26 +++++++++++++++----------- 3 files changed, 18 insertions(+), 12 deletions(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index a6c57998b0..b8abef38a8 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -258,6 +258,8 @@ namespace osu.Game { dependencies.CacheAs(this); + SentryLogger.AttachUser(API.LocalUser); + dependencies.Cache(osuLogo = new OsuLogo { Alpha = 0 }); // bind config int to database RulesetInfo diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 3f0610e9aa..324fcada89 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -125,7 +125,7 @@ namespace osu.Game protected MusicController MusicController { get; private set; } - protected internal IAPIProvider API { get; protected set; } + protected IAPIProvider API { get; set; } protected Storage Storage { get; set; } diff --git a/osu.Game/Utils/SentryLogger.cs b/osu.Game/Utils/SentryLogger.cs index 5cecc4d776..92f2902c0e 100644 --- a/osu.Game/Utils/SentryLogger.cs +++ b/osu.Game/Utils/SentryLogger.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. +#nullable enable + using System; +using System.Diagnostics; using System.IO; using System.Net; -using JetBrains.Annotations; using osu.Framework.Bindables; using osu.Framework.Logging; using osu.Game.Online.API.Requests.Responses; @@ -19,10 +21,9 @@ namespace osu.Game.Utils { private SentryClient sentry; private Scope sentryScope; - private Exception lastException; + private Exception? lastException; - [UsedImplicitly] - private readonly IBindable localUser; + private IBindable? localUser; public SentryLogger(OsuGame game) { @@ -40,16 +41,21 @@ namespace osu.Game.Utils sentryScope = new Scope(options); Logger.NewEntry += processLogEntry; + } - localUser = game.API.LocalUser.GetBoundCopy(); - localUser.BindValueChanged(user => + public void AttachUser(IBindable user) + { + Debug.Assert(localUser == null); + + localUser = user.GetBoundCopy(); + localUser.BindValueChanged(u => { sentryScope.User = new User { - Username = user.NewValue.Username, - Id = user.NewValue.Id.ToString(), + Username = u.NewValue.Username, + Id = u.NewValue.Id.ToString(), }; - }); + }, true); } private void processLogEntry(LogEntry entry) @@ -137,8 +143,6 @@ namespace osu.Game.Utils protected virtual void Dispose(bool isDisposing) { Logger.NewEntry -= processLogEntry; - sentry = null; - sentryScope = null; } #endregion From 9734d778f4bda85e2f38cd03e326c09cb3c40ac7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 10 May 2022 14:44:54 +0900 Subject: [PATCH 056/147] Update sentry SDK usage in line with more recent specifications --- osu.Game/Utils/SentryLogger.cs | 33 ++++++++++++++++----------------- 1 file changed, 16 insertions(+), 17 deletions(-) diff --git a/osu.Game/Utils/SentryLogger.cs b/osu.Game/Utils/SentryLogger.cs index 92f2902c0e..02bcdd281a 100644 --- a/osu.Game/Utils/SentryLogger.cs +++ b/osu.Game/Utils/SentryLogger.cs @@ -19,26 +19,24 @@ namespace osu.Game.Utils /// public class SentryLogger : IDisposable { - private SentryClient sentry; - private Scope sentryScope; private Exception? lastException; private IBindable? localUser; + private readonly IDisposable? sentrySession; + public SentryLogger(OsuGame game) { - if (!game.IsDeployedBuild) return; - - var options = new SentryOptions + sentrySession = SentrySdk.Init(options => { - Dsn = "https://ad9f78529cef40ac874afb95a9aca04e@sentry.ppy.sh/2", - AutoSessionTracking = true, - IsEnvironmentUser = false, - Release = game.Version - }; + // Not setting the dsn will completely disable sentry. + if (game.IsDeployedBuild) + options.Dsn = "https://ad9f78529cef40ac874afb95a9aca04e@sentry.ppy.sh/2"; - sentry = new SentryClient(options); - sentryScope = new Scope(options); + options.AutoSessionTracking = true; + options.IsEnvironmentUser = false; + options.Release = game.Version; + }); Logger.NewEntry += processLogEntry; } @@ -50,11 +48,11 @@ namespace osu.Game.Utils localUser = user.GetBoundCopy(); localUser.BindValueChanged(u => { - sentryScope.User = new User + SentrySdk.ConfigureScope(scope => scope.User = new User { Username = u.NewValue.Username, Id = u.NewValue.Id.ToString(), - }; + }); }, true); } @@ -73,14 +71,14 @@ namespace osu.Game.Utils lastException = exception; - sentry.CaptureEvent(new SentryEvent(exception) + SentrySdk.CaptureEvent(new SentryEvent(exception) { Message = entry.Message, Level = getSentryLevel(entry.Level), - }, sentryScope); + }); } else - sentryScope.AddBreadcrumb(DateTimeOffset.Now, entry.Message, entry.Target.ToString(), "navigation"); + SentrySdk.AddBreadcrumb(entry.Message, entry.Target.ToString(), "navigation"); } private SentryLevel? getSentryLevel(LogLevel entryLevel) @@ -143,6 +141,7 @@ namespace osu.Game.Utils protected virtual void Dispose(bool isDisposing) { Logger.NewEntry -= processLogEntry; + sentrySession?.Dispose(); } #endregion From 99e6d56508799132912238edf898df000f33033a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 10 May 2022 14:45:55 +0900 Subject: [PATCH 057/147] Add finalizer to sentry logger for safety --- osu.Game/Utils/SentryLogger.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Utils/SentryLogger.cs b/osu.Game/Utils/SentryLogger.cs index 02bcdd281a..c6429b6b2c 100644 --- a/osu.Game/Utils/SentryLogger.cs +++ b/osu.Game/Utils/SentryLogger.cs @@ -41,6 +41,8 @@ namespace osu.Game.Utils Logger.NewEntry += processLogEntry; } + ~SentryLogger() => Dispose(false); + public void AttachUser(IBindable user) { Debug.Assert(localUser == null); From c6112b3ae78becb6980053ae047258c103a4db8e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 10 May 2022 15:07:02 +0900 Subject: [PATCH 058/147] Add unhandled exception marking --- osu.Game/Utils/SentryLogger.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/osu.Game/Utils/SentryLogger.cs b/osu.Game/Utils/SentryLogger.cs index c6429b6b2c..728d6ab9be 100644 --- a/osu.Game/Utils/SentryLogger.cs +++ b/osu.Game/Utils/SentryLogger.cs @@ -11,6 +11,7 @@ using osu.Framework.Bindables; using osu.Framework.Logging; using osu.Game.Online.API.Requests.Responses; using Sentry; +using Sentry.Protocol; namespace osu.Game.Utils { @@ -73,6 +74,13 @@ namespace osu.Game.Utils lastException = exception; + // framework does some weird exception redirection which means sentry does not see unhandled exceptions using its automatic methods. + // but all unhandled exceptions still arrive via this pathway. we just need to mark them as unhandled for tagging purposes. + // easiest solution is to check the message matches what the framework logs this as. + // see https://github.com/ppy/osu-framework/blob/f932f8df053f0011d755c95ad9a2ed61b94d136b/osu.Framework/Platform/GameHost.cs#L336 + bool wasHandled = entry.Message != @"An unhandled exception has occurred."; + exception.Data[Mechanism.HandledKey] = wasHandled; + SentrySdk.CaptureEvent(new SentryEvent(exception) { Message = entry.Message, From 363643a16d64777ffd0daf681507ec0e53d003c3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 10 May 2022 15:08:49 +0900 Subject: [PATCH 059/147] Remove sentry logger debounce This is probably going to result in a high quantity of exceptions, but I think this is fine. We can add rules as we go to not log certain exception types. --- osu.Game/Utils/SentryLogger.cs | 7 ------- 1 file changed, 7 deletions(-) diff --git a/osu.Game/Utils/SentryLogger.cs b/osu.Game/Utils/SentryLogger.cs index 728d6ab9be..170d8e7cb0 100644 --- a/osu.Game/Utils/SentryLogger.cs +++ b/osu.Game/Utils/SentryLogger.cs @@ -20,8 +20,6 @@ namespace osu.Game.Utils /// public class SentryLogger : IDisposable { - private Exception? lastException; - private IBindable? localUser; private readonly IDisposable? sentrySession; @@ -69,11 +67,6 @@ namespace osu.Game.Utils { if (!shouldSubmitException(exception)) return; - // since we let unhandled exceptions go ignored at times, we want to ensure they don't get submitted on subsequent reports. - if (lastException != null && lastException.Message == exception.Message && exception.StackTrace.StartsWith(lastException.StackTrace, StringComparison.Ordinal)) return; - - lastException = exception; - // framework does some weird exception redirection which means sentry does not see unhandled exceptions using its automatic methods. // but all unhandled exceptions still arrive via this pathway. we just need to mark them as unhandled for tagging purposes. // easiest solution is to check the message matches what the framework logs this as. From 216c68e6d018246a37870fa2ada898f7def2a080 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 10 May 2022 15:19:43 +0900 Subject: [PATCH 060/147] Add unobserved exception hinting --- osu.Game/Utils/SentryLogger.cs | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/osu.Game/Utils/SentryLogger.cs b/osu.Game/Utils/SentryLogger.cs index 170d8e7cb0..96affd85a7 100644 --- a/osu.Game/Utils/SentryLogger.cs +++ b/osu.Game/Utils/SentryLogger.cs @@ -71,8 +71,22 @@ namespace osu.Game.Utils // but all unhandled exceptions still arrive via this pathway. we just need to mark them as unhandled for tagging purposes. // easiest solution is to check the message matches what the framework logs this as. // see https://github.com/ppy/osu-framework/blob/f932f8df053f0011d755c95ad9a2ed61b94d136b/osu.Framework/Platform/GameHost.cs#L336 - bool wasHandled = entry.Message != @"An unhandled exception has occurred."; - exception.Data[Mechanism.HandledKey] = wasHandled; + bool wasUnhandled = entry.Message == @"An unhandled error has occurred."; + bool wasUnobserved = entry.Message == @"An unobserved error has occurred."; + + if (wasUnobserved) + { + // see https://github.com/getsentry/sentry-dotnet/blob/c6a660b1affc894441c63df2695a995701671744/src/Sentry/Integrations/TaskUnobservedTaskExceptionIntegration.cs#L39 + exception.Data[Mechanism.MechanismKey] = @"UnobservedTaskException"; + } + + if (wasUnhandled) + { + // see https://github.com/getsentry/sentry-dotnet/blob/main/src/Sentry/Integrations/AppDomainUnhandledExceptionIntegration.cs#L38-L39 + exception.Data[Mechanism.MechanismKey] = @"AppDomain.UnhandledException"; + } + + exception.Data[Mechanism.HandledKey] = !wasUnhandled; SentrySdk.CaptureEvent(new SentryEvent(exception) { From 6a49eb68759d89e4d001866a7ae4fcbb7addc8d5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 10 May 2022 16:14:04 +0900 Subject: [PATCH 061/147] Add breadcrumb level mappings --- osu.Game/Utils/SentryLogger.cs | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/osu.Game/Utils/SentryLogger.cs b/osu.Game/Utils/SentryLogger.cs index 96affd85a7..ad4bcf6274 100644 --- a/osu.Game/Utils/SentryLogger.cs +++ b/osu.Game/Utils/SentryLogger.cs @@ -95,10 +95,31 @@ namespace osu.Game.Utils }); } else - SentrySdk.AddBreadcrumb(entry.Message, entry.Target.ToString(), "navigation"); + SentrySdk.AddBreadcrumb(entry.Message, entry.Target.ToString(), "navigation", level: getBreadcrumbLevel(entry.Level)); } - private SentryLevel? getSentryLevel(LogLevel entryLevel) + private BreadcrumbLevel getBreadcrumbLevel(LogLevel entryLevel) + { + switch (entryLevel) + { + case LogLevel.Debug: + return BreadcrumbLevel.Debug; + + case LogLevel.Verbose: + return BreadcrumbLevel.Info; + + case LogLevel.Important: + return BreadcrumbLevel.Warning; + + case LogLevel.Error: + return BreadcrumbLevel.Error; + + default: + throw new ArgumentOutOfRangeException(nameof(entryLevel), entryLevel, null); + } + } + + private SentryLevel getSentryLevel(LogLevel entryLevel) { switch (entryLevel) { From 14a21e92244030a6f1fa758237f9733776b0c6b7 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 10 May 2022 11:41:41 +0300 Subject: [PATCH 062/147] Workaround interface mocks in `TestSceneFirstRunSetupOverlay` breaking with hot reload --- .../TestSceneFirstRunSetupOverlay.cs | 36 ++++++++++++++++--- 1 file changed, 32 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneFirstRunSetupOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneFirstRunSetupOverlay.cs index 39298f56ba..905f53c165 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneFirstRunSetupOverlay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneFirstRunSetupOverlay.cs @@ -4,9 +4,11 @@ using System; using System.Collections.Generic; using System.Linq; +using JetBrains.Annotations; using Moq; using NUnit.Framework; using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Graphics.Containers; using osu.Framework.Screens; using osu.Framework.Testing; @@ -25,9 +27,9 @@ namespace osu.Game.Tests.Visual.UserInterface { private FirstRunSetupOverlay overlay; - private readonly Mock performer = new Mock(); + private readonly Mock performer = new Mock(); - private readonly Mock notificationOverlay = new Mock(); + private readonly Mock notificationOverlay = new Mock(); private Notification lastNotification; @@ -37,8 +39,8 @@ namespace osu.Game.Tests.Visual.UserInterface private void load() { Dependencies.Cache(LocalConfig = new OsuConfigManager(LocalStorage)); - Dependencies.CacheAs(performer.Object); - Dependencies.CacheAs(notificationOverlay.Object); + Dependencies.CacheAs(performer.Object); + Dependencies.CacheAs(notificationOverlay.Object); } [SetUpSteps] @@ -196,5 +198,31 @@ namespace osu.Game.Tests.Visual.UserInterface AddAssert("overlay shown", () => overlay.State.Value == Visibility.Visible); AddAssert("is resumed", () => overlay.CurrentScreen is ScreenBeatmaps); } + + // interface mocks break hot reload, mocking this stub implementation instead works around it. + // see: https://github.com/moq/moq4/issues/1252 + [UsedImplicitly] + public class TestNotificationOverlay : INotificationOverlay + { + public virtual void Post(Notification notification) + { + } + + public virtual void Hide() + { + } + + public virtual IBindable UnreadCount => null; + } + + // interface mocks break hot reload, mocking this stub implementation instead works around it. + // see: https://github.com/moq/moq4/issues/1252 + [UsedImplicitly] + public class TestPerformerFromScreenRunner : IPerformFromScreenRunner + { + public virtual void PerformFromScreen(Action action, IEnumerable validScreens = null) + { + } + } } } From 9aadc274bfe5578d2dabe2388ee9f3af4928addd Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 10 May 2022 16:43:38 +0900 Subject: [PATCH 063/147] Show first run dialog on first run of the game --- .../Visual/UserInterface/TestSceneFirstRunSetupOverlay.cs | 1 - osu.Game/Overlays/FirstRunSetupOverlay.cs | 6 ++---- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneFirstRunSetupOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneFirstRunSetupOverlay.cs index 905f53c165..48b5690243 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneFirstRunSetupOverlay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneFirstRunSetupOverlay.cs @@ -74,7 +74,6 @@ namespace osu.Game.Tests.Visual.UserInterface } [Test] - [Ignore("Enable when first run setup is being displayed on first run.")] public void TestDoesntOpenOnSecondRun() { AddStep("set first run", () => LocalConfig.SetValue(OsuSetting.ShowFirstRunSetup, true)); diff --git a/osu.Game/Overlays/FirstRunSetupOverlay.cs b/osu.Game/Overlays/FirstRunSetupOverlay.cs index 607bef76dd..df1e094114 100644 --- a/osu.Game/Overlays/FirstRunSetupOverlay.cs +++ b/osu.Game/Overlays/FirstRunSetupOverlay.cs @@ -171,8 +171,7 @@ namespace osu.Game.Overlays config.BindWith(OsuSetting.ShowFirstRunSetup, showFirstRunSetup); - // TODO: uncomment when happy with the whole flow. - // if (showFirstRunSetup.Value) Show(); + if (showFirstRunSetup.Value) Show(); } public override bool OnPressed(KeyBindingPressEvent e) @@ -304,8 +303,7 @@ namespace osu.Game.Overlays } else { - // TODO: uncomment when happy with the whole flow. - // showFirstRunSetup.Value = false; + showFirstRunSetup.Value = false; currentStepIndex = null; Hide(); } From bcce9c5e6772911ff817216c4424ad0ff596e7a1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 10 May 2022 17:01:36 +0900 Subject: [PATCH 064/147] Limit the width of first run overlay content --- osu.Game/Overlays/FirstRunSetupOverlay.cs | 71 ++++++++++++++--------- 1 file changed, 43 insertions(+), 28 deletions(-) diff --git a/osu.Game/Overlays/FirstRunSetupOverlay.cs b/osu.Game/Overlays/FirstRunSetupOverlay.cs index df1e094114..befa80e839 100644 --- a/osu.Game/Overlays/FirstRunSetupOverlay.cs +++ b/osu.Game/Overlays/FirstRunSetupOverlay.cs @@ -62,7 +62,7 @@ namespace osu.Game.Overlays typeof(ScreenBehaviour), }; - private Container stackContainer = null!; + private Container screenContent = null!; private Bindable? overlayActivationMode; @@ -86,36 +86,51 @@ namespace osu.Game.Overlays Anchor = Anchor.Centre, Origin = Anchor.Centre, RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding + Padding = new MarginPadding { Bottom = 20, }, + Child = new GridContainer() { - Horizontal = 70 * 1.2f, - Bottom = 20, - }, - Child = new InputBlockingContainer - { - Masking = true, - CornerRadius = 14, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, RelativeSizeAxes = Axes.Both, - Children = new Drawable[] + ColumnDimensions = new[] { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = ColourProvider.Background6, - }, - stackContainer = new Container - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding - { - Vertical = 20, - Horizontal = 70, - }, - } + new Dimension(), + new Dimension(minSize: 640, maxSize: 800), + new Dimension(), }, - }, + Content = new[] + { + new[] + { + Empty(), + new InputBlockingContainer + { + Masking = true, + CornerRadius = 14, + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = ColourProvider.Background6, + }, + new Container + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding + { + Vertical = 20, + Horizontal = 20, + }, + Child = screenContent = new Container { RelativeSizeAxes = Axes.Both, }, + }, + }, + }, + Empty(), + }, + } + } }, }); @@ -268,7 +283,7 @@ namespace osu.Game.Overlays { Debug.Assert(currentStepIndex == null); - stackContainer.Child = stack = new ScreenStack + screenContent.Child = stack = new ScreenStack { RelativeSizeAxes = Axes.Both, }; From e5204e565dae9946543ca23c72119be7f26e0540 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 10 May 2022 17:23:12 +0900 Subject: [PATCH 065/147] Move horizontal padding to content level to better align scrollbar --- .../FirstRunSetup/FirstRunSetupScreen.cs | 34 +++++++++++-------- osu.Game/Overlays/FirstRunSetupOverlay.cs | 6 +--- 2 files changed, 21 insertions(+), 19 deletions(-) diff --git a/osu.Game/Overlays/FirstRunSetup/FirstRunSetupScreen.cs b/osu.Game/Overlays/FirstRunSetup/FirstRunSetupScreen.cs index 1f18d181cb..2990948199 100644 --- a/osu.Game/Overlays/FirstRunSetup/FirstRunSetupScreen.cs +++ b/osu.Game/Overlays/FirstRunSetup/FirstRunSetupScreen.cs @@ -33,23 +33,29 @@ namespace osu.Game.Overlays.FirstRunSetup new OsuScrollContainer(Direction.Vertical) { RelativeSizeAxes = Axes.Both, - ScrollbarOverlapsContent = false, - Children = new Drawable[] + Masking = false, + Child = new Container { - new OsuSpriteText + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Padding = new MarginPadding { Horizontal = 30 }, + Children = new Drawable[] { - Text = this.GetLocalisableDescription(), - Font = OsuFont.Default.With(size: header_size), - Colour = OverlayColourProvider.Light1, + new OsuSpriteText + { + Text = this.GetLocalisableDescription(), + Font = OsuFont.Default.With(size: header_size), + Colour = OverlayColourProvider.Light1, + }, + Content = new FillFlowContainer + { + Y = header_size + spacing, + Spacing = new Vector2(spacing), + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + } }, - Content = new FillFlowContainer - { - Y = header_size + spacing, - Spacing = new Vector2(spacing), - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Direction = FillDirection.Vertical, - } }, } }; diff --git a/osu.Game/Overlays/FirstRunSetupOverlay.cs b/osu.Game/Overlays/FirstRunSetupOverlay.cs index befa80e839..19a4c09473 100644 --- a/osu.Game/Overlays/FirstRunSetupOverlay.cs +++ b/osu.Game/Overlays/FirstRunSetupOverlay.cs @@ -118,11 +118,7 @@ namespace osu.Game.Overlays new Container { RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding - { - Vertical = 20, - Horizontal = 20, - }, + Padding = new MarginPadding { Vertical = 20 }, Child = screenContent = new Container { RelativeSizeAxes = Axes.Both, }, }, }, From 61313b69ecd40049dbc13b502e21a0e4cbeb312d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 10 May 2022 17:33:37 +0900 Subject: [PATCH 066/147] Standardise font sizes in first run overlay screens --- .../Overlays/FirstRunSetup/FirstRunSetupScreen.cs | 9 ++++++--- osu.Game/Overlays/FirstRunSetup/ScreenBeatmaps.cs | 14 +++++++------- osu.Game/Overlays/FirstRunSetup/ScreenBehaviour.cs | 2 +- osu.Game/Overlays/FirstRunSetup/ScreenUIScale.cs | 2 +- osu.Game/Overlays/FirstRunSetup/ScreenWelcome.cs | 2 +- 5 files changed, 16 insertions(+), 13 deletions(-) diff --git a/osu.Game/Overlays/FirstRunSetup/FirstRunSetupScreen.cs b/osu.Game/Overlays/FirstRunSetup/FirstRunSetupScreen.cs index 2990948199..2cfa7cd164 100644 --- a/osu.Game/Overlays/FirstRunSetup/FirstRunSetupScreen.cs +++ b/osu.Game/Overlays/FirstRunSetup/FirstRunSetupScreen.cs @@ -19,13 +19,16 @@ namespace osu.Game.Overlays.FirstRunSetup protected FillFlowContainer Content { get; private set; } + protected const float CONTENT_FONT_SIZE = 16; + + protected const float HEADER_FONT_SIZE = 24; + [Resolved] protected OverlayColourProvider OverlayColourProvider { get; private set; } [BackgroundDependencyLoader] private void load() { - const float header_size = 40; const float spacing = 20; InternalChildren = new Drawable[] @@ -44,12 +47,12 @@ namespace osu.Game.Overlays.FirstRunSetup new OsuSpriteText { Text = this.GetLocalisableDescription(), - Font = OsuFont.Default.With(size: header_size), + Font = OsuFont.TorusAlternate.With(size: HEADER_FONT_SIZE), Colour = OverlayColourProvider.Light1, }, Content = new FillFlowContainer { - Y = header_size + spacing, + Y = HEADER_FONT_SIZE + spacing, Spacing = new Vector2(spacing), RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, diff --git a/osu.Game/Overlays/FirstRunSetup/ScreenBeatmaps.cs b/osu.Game/Overlays/FirstRunSetup/ScreenBeatmaps.cs index 190a0badab..66acdca8c7 100644 --- a/osu.Game/Overlays/FirstRunSetup/ScreenBeatmaps.cs +++ b/osu.Game/Overlays/FirstRunSetup/ScreenBeatmaps.cs @@ -46,11 +46,11 @@ namespace osu.Game.Overlays.FirstRunSetup [BackgroundDependencyLoader(permitNulls: true)] private void load(LegacyImportManager? legacyImportManager) { - Vector2 buttonSize = new Vector2(500, 60); + Vector2 buttonSize = new Vector2(400, 50); Content.Children = new Drawable[] { - new OsuTextFlowContainer(cp => cp.Font = OsuFont.Default.With(size: 20)) + new OsuTextFlowContainer(cp => cp.Font = OsuFont.Default.With(size: CONTENT_FONT_SIZE)) { Colour = OverlayColourProvider.Content1, Text = FirstRunSetupBeatmapScreenStrings.Description, @@ -63,7 +63,7 @@ namespace osu.Game.Overlays.FirstRunSetup Height = 30, Children = new Drawable[] { - currentlyLoadedBeatmaps = new OsuTextFlowContainer(cp => cp.Font = OsuFont.Default.With(size: 24, weight: FontWeight.SemiBold)) + currentlyLoadedBeatmaps = new OsuTextFlowContainer(cp => cp.Font = OsuFont.Default.With(size: HEADER_FONT_SIZE, weight: FontWeight.SemiBold)) { Colour = OverlayColourProvider.Content2, TextAnchor = Anchor.Centre, @@ -73,7 +73,7 @@ namespace osu.Game.Overlays.FirstRunSetup }, } }, - new OsuTextFlowContainer(cp => cp.Font = OsuFont.Default.With(size: 20)) + new OsuTextFlowContainer(cp => cp.Font = OsuFont.Default.With(size: CONTENT_FONT_SIZE)) { Colour = OverlayColourProvider.Content1, Text = FirstRunSetupBeatmapScreenStrings.TutorialDescription, @@ -89,7 +89,7 @@ namespace osu.Game.Overlays.FirstRunSetup Text = FirstRunSetupBeatmapScreenStrings.TutorialButton, Action = downloadTutorial }, - new OsuTextFlowContainer(cp => cp.Font = OsuFont.Default.With(size: 20)) + new OsuTextFlowContainer(cp => cp.Font = OsuFont.Default.With(size: CONTENT_FONT_SIZE)) { Colour = OverlayColourProvider.Content1, Text = FirstRunSetupBeatmapScreenStrings.BundledDescription, @@ -105,7 +105,7 @@ namespace osu.Game.Overlays.FirstRunSetup Text = FirstRunSetupBeatmapScreenStrings.BundledButton, Action = downloadBundled }, - new OsuTextFlowContainer(cp => cp.Font = OsuFont.Default.With(size: 20)) + new OsuTextFlowContainer(cp => cp.Font = OsuFont.Default.With(size: CONTENT_FONT_SIZE)) { Colour = OverlayColourProvider.Content1, Text = "If you have an existing osu! install, you can also choose to import your existing beatmap collection.", @@ -131,7 +131,7 @@ namespace osu.Game.Overlays.FirstRunSetup })); } }, - new OsuTextFlowContainer(cp => cp.Font = OsuFont.Default.With(size: 20)) + new OsuTextFlowContainer(cp => cp.Font = OsuFont.Default.With(size: CONTENT_FONT_SIZE)) { Colour = OverlayColourProvider.Content1, Text = FirstRunSetupBeatmapScreenStrings.ObtainMoreBeatmaps, diff --git a/osu.Game/Overlays/FirstRunSetup/ScreenBehaviour.cs b/osu.Game/Overlays/FirstRunSetup/ScreenBehaviour.cs index dc3d40ad95..9d426bd3fa 100644 --- a/osu.Game/Overlays/FirstRunSetup/ScreenBehaviour.cs +++ b/osu.Game/Overlays/FirstRunSetup/ScreenBehaviour.cs @@ -26,7 +26,7 @@ namespace osu.Game.Overlays.FirstRunSetup { Content.Children = new Drawable[] { - new OsuTextFlowContainer(cp => cp.Font = OsuFont.Default.With(size: 24)) + new OsuTextFlowContainer(cp => cp.Font = OsuFont.Default.With(size: CONTENT_FONT_SIZE)) { Text = FirstRunSetupOverlayStrings.BehaviourDescription, RelativeSizeAxes = Axes.X, diff --git a/osu.Game/Overlays/FirstRunSetup/ScreenUIScale.cs b/osu.Game/Overlays/FirstRunSetup/ScreenUIScale.cs index 152d67ab27..24e113e6a9 100644 --- a/osu.Game/Overlays/FirstRunSetup/ScreenUIScale.cs +++ b/osu.Game/Overlays/FirstRunSetup/ScreenUIScale.cs @@ -37,7 +37,7 @@ namespace osu.Game.Overlays.FirstRunSetup { Content.Children = new Drawable[] { - new OsuTextFlowContainer(cp => cp.Font = OsuFont.Default.With(size: 24)) + new OsuTextFlowContainer(cp => cp.Font = OsuFont.Default.With(size: CONTENT_FONT_SIZE)) { Text = FirstRunSetupOverlayStrings.UIScaleDescription, RelativeSizeAxes = Axes.X, diff --git a/osu.Game/Overlays/FirstRunSetup/ScreenWelcome.cs b/osu.Game/Overlays/FirstRunSetup/ScreenWelcome.cs index 10e15a7555..420d630857 100644 --- a/osu.Game/Overlays/FirstRunSetup/ScreenWelcome.cs +++ b/osu.Game/Overlays/FirstRunSetup/ScreenWelcome.cs @@ -18,7 +18,7 @@ namespace osu.Game.Overlays.FirstRunSetup { Content.Children = new Drawable[] { - new OsuTextFlowContainer(cp => cp.Font = OsuFont.Default.With(size: 20)) + new OsuTextFlowContainer(cp => cp.Font = OsuFont.Default.With(size: CONTENT_FONT_SIZE)) { Text = FirstRunSetupOverlayStrings.WelcomeDescription, RelativeSizeAxes = Axes.X, From 63b9e01d384eb8fda2c5439ab98807b1be7244e5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 10 May 2022 17:35:43 +0900 Subject: [PATCH 067/147] Fix behaviour screen using old style buttons --- osu.Game/Overlays/FirstRunSetup/ScreenBehaviour.cs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/osu.Game/Overlays/FirstRunSetup/ScreenBehaviour.cs b/osu.Game/Overlays/FirstRunSetup/ScreenBehaviour.cs index 9d426bd3fa..1a88e6a842 100644 --- a/osu.Game/Overlays/FirstRunSetup/ScreenBehaviour.cs +++ b/osu.Game/Overlays/FirstRunSetup/ScreenBehaviour.cs @@ -9,7 +9,7 @@ using osu.Framework.Localisation; using osu.Framework.Testing; using osu.Game.Graphics; using osu.Game.Graphics.Containers; -using osu.Game.Graphics.UserInterface; +using osu.Game.Graphics.UserInterfaceV2; using osu.Game.Localisation; using osu.Game.Overlays.Settings; using osu.Game.Overlays.Settings.Sections; @@ -22,7 +22,7 @@ namespace osu.Game.Overlays.FirstRunSetup private SearchContainer searchContainer; [BackgroundDependencyLoader] - private void load() + private void load(OsuColour colours) { Content.Children = new Drawable[] { @@ -50,7 +50,7 @@ namespace osu.Game.Overlays.FirstRunSetup { new[] { - new TriangleButton + new RoundedButton { Anchor = Anchor.TopLeft, Origin = Anchor.TopLeft, @@ -59,10 +59,11 @@ namespace osu.Game.Overlays.FirstRunSetup Action = applyStandard, }, Empty(), - new DangerousTriangleButton + new RoundedButton { Anchor = Anchor.TopRight, Origin = Anchor.TopRight, + BackgroundColour = colours.Pink3, Text = FirstRunSetupOverlayStrings.ClassicDefaults, RelativeSizeAxes = Axes.X, Action = applyClassic From 493798ae5e5e4ba61e5654b2623eab4a249af750 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 10 May 2022 17:44:23 +0900 Subject: [PATCH 068/147] Fix nested ui scale example screens no longer fitting --- osu.Game/Overlays/FirstRunSetup/ScreenUIScale.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/FirstRunSetup/ScreenUIScale.cs b/osu.Game/Overlays/FirstRunSetup/ScreenUIScale.cs index 24e113e6a9..4a44a6d391 100644 --- a/osu.Game/Overlays/FirstRunSetup/ScreenUIScale.cs +++ b/osu.Game/Overlays/FirstRunSetup/ScreenUIScale.cs @@ -35,6 +35,8 @@ namespace osu.Game.Overlays.FirstRunSetup [BackgroundDependencyLoader] private void load(OsuConfigManager config) { + const float screen_width = 640; + Content.Children = new Drawable[] { new OsuTextFlowContainer(cp => cp.Font = OsuFont.Default.With(size: CONTENT_FONT_SIZE)) @@ -54,7 +56,7 @@ namespace osu.Game.Overlays.FirstRunSetup Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, RelativeSizeAxes = Axes.None, - Size = new Vector2(960, 960 / 16f * 9 / 2), + Size = new Vector2(screen_width, screen_width / 16f * 9 / 2), Children = new Drawable[] { new GridContainer From 25c6226ca9cb7c82ef9a66e4c12f45f13ec7e732 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 10 May 2022 17:47:04 +0900 Subject: [PATCH 069/147] Adjust transition length for a more seamless screen change --- osu.Game/Overlays/FirstRunSetup/FirstRunSetupScreen.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/FirstRunSetup/FirstRunSetupScreen.cs b/osu.Game/Overlays/FirstRunSetup/FirstRunSetupScreen.cs index 2cfa7cd164..d1ea91e51a 100644 --- a/osu.Game/Overlays/FirstRunSetup/FirstRunSetupScreen.cs +++ b/osu.Game/Overlays/FirstRunSetup/FirstRunSetupScreen.cs @@ -68,7 +68,7 @@ namespace osu.Game.Overlays.FirstRunSetup { base.OnEntering(e); this - .FadeInFromZero(500) + .FadeInFromZero(100) .MoveToX(offset) .MoveToX(0, 500, Easing.OutQuint); } @@ -77,7 +77,7 @@ namespace osu.Game.Overlays.FirstRunSetup { base.OnResuming(e); this - .FadeInFromZero(500) + .FadeInFromZero(100) .MoveToX(0, 500, Easing.OutQuint); } From a93c63b2b5b7d0119b3fb5ee97bee8232d593854 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 10 May 2022 18:04:10 +0900 Subject: [PATCH 070/147] Add loading spinner when loading first run screens --- osu.Game/Overlays/FirstRunSetupOverlay.cs | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/FirstRunSetupOverlay.cs b/osu.Game/Overlays/FirstRunSetupOverlay.cs index 19a4c09473..c0eed14c99 100644 --- a/osu.Game/Overlays/FirstRunSetupOverlay.cs +++ b/osu.Game/Overlays/FirstRunSetupOverlay.cs @@ -15,6 +15,7 @@ using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Events; using osu.Framework.Localisation; using osu.Framework.Screens; +using osu.Framework.Threading; using osu.Game.Configuration; using osu.Game.Graphics; using osu.Game.Graphics.UserInterface; @@ -68,6 +69,9 @@ namespace osu.Game.Overlays private Container content = null!; + private LoadingSpinner loading = null!; + private ScheduledDelegate? loadingShowDelegate; + public FirstRunSetupOverlay() : base(OverlayColourScheme.Purple) { @@ -115,6 +119,7 @@ namespace osu.Game.Overlays RelativeSizeAxes = Axes.Both, Colour = ColourProvider.Background6, }, + loading = new LoadingSpinner(), new Container { RelativeSizeAxes = Axes.Both, @@ -310,7 +315,16 @@ namespace osu.Game.Overlays if (currentStepIndex < steps.Length) { - stack.Push((Screen)Activator.CreateInstance(steps[currentStepIndex.Value])); + var nextScreen = (Screen)Activator.CreateInstance(steps[currentStepIndex.Value]); + + loadingShowDelegate = Scheduler.AddDelayed(() => loading.Show(), 200); + nextScreen.OnLoadComplete += _ => + { + loadingShowDelegate?.Cancel(); + loading.Hide(); + }; + + stack.Push(nextScreen); } else { From a578f7a406a049f8548af177b630c89a6ba32c8a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 10 May 2022 18:08:17 +0900 Subject: [PATCH 071/147] Force nested screens to load synchronously --- osu.Game/Overlays/FirstRunSetup/ScreenUIScale.cs | 15 +++++++++------ osu.Game/Screens/OsuScreenStack.cs | 7 +++++++ 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/osu.Game/Overlays/FirstRunSetup/ScreenUIScale.cs b/osu.Game/Overlays/FirstRunSetup/ScreenUIScale.cs index 4a44a6d391..8452691bb5 100644 --- a/osu.Game/Overlays/FirstRunSetup/ScreenUIScale.cs +++ b/osu.Game/Overlays/FirstRunSetup/ScreenUIScale.cs @@ -125,6 +125,7 @@ namespace osu.Game.Overlays.FirstRunSetup private class SampleScreenContainer : CompositeDrawable { + private readonly OsuScreen screen; // Minimal isolation from main game. [Cached] @@ -144,6 +145,12 @@ namespace osu.Game.Overlays.FirstRunSetup public override bool PropagatePositionalInputSubTree => false; public override bool PropagateNonPositionalInputSubTree => false; + public SampleScreenContainer(OsuScreen screen) + { + this.screen = screen; + RelativeSizeAxes = Axes.Both; + } + [BackgroundDependencyLoader] private void load(AudioManager audio, TextureStore textures, RulesetStore rulesets) { @@ -151,13 +158,8 @@ namespace osu.Game.Overlays.FirstRunSetup Beatmap.Value.LoadTrack(); Ruleset.Value = rulesets.AvailableRulesets.First(); - } - public SampleScreenContainer(Screen screen) - { OsuScreenStack stack; - RelativeSizeAxes = Axes.Both; - OsuLogo logo; Padding = new MarginPadding(5); @@ -191,7 +193,8 @@ namespace osu.Game.Overlays.FirstRunSetup }, }; - stack.Push(screen); + // intentionally load synchronously so it is included in the initial load of the first run screen. + stack.PushSynchronously(screen); } } } diff --git a/osu.Game/Screens/OsuScreenStack.cs b/osu.Game/Screens/OsuScreenStack.cs index ebbcbd7650..18b16ba865 100644 --- a/osu.Game/Screens/OsuScreenStack.cs +++ b/osu.Game/Screens/OsuScreenStack.cs @@ -29,6 +29,13 @@ namespace osu.Game.Screens ScreenExited += ScreenChanged; } + public void PushSynchronously(OsuScreen screen) + { + LoadComponent(screen); + + Push(screen); + } + private void screenPushed(IScreen prev, IScreen next) { if (LoadState < LoadState.Ready) From cac6d5569c8dbd1282330ffadf5a2d20bb0d0cb5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 10 May 2022 19:07:00 +0900 Subject: [PATCH 072/147] Fix incorrect variable reference in log output --- osu.Game/OsuGameBase.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index c5b69a3637..3a35bce05c 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -524,7 +524,7 @@ namespace osu.Game { bool continueExecution = Interlocked.Decrement(ref allowableExceptions) >= 0; - Logger.Log($"Unhandled exception has been {(continueExecution ? $"allowed with {SoftHandledExceptions} more allowable exceptions" : "denied")} ."); + Logger.Log($"Unhandled exception has been {(continueExecution ? $"allowed with {allowableExceptions} more allowable exceptions" : "denied")} ."); // restore the stock of allowable exceptions after a short delay. Task.Delay(1000).ContinueWith(_ => Interlocked.Increment(ref allowableExceptions)); From 4d22f262667972c4bf20cf1c117d8a7e1e5f2f45 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 10 May 2022 19:07:07 +0900 Subject: [PATCH 073/147] Rename property and improve xmldoc --- osu.Game.Tournament/TournamentGame.cs | 2 +- osu.Game/OsuGame.cs | 2 +- osu.Game/OsuGameBase.cs | 11 ++++++++--- 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/osu.Game.Tournament/TournamentGame.cs b/osu.Game.Tournament/TournamentGame.cs index d9db0e6d20..042e660122 100644 --- a/osu.Game.Tournament/TournamentGame.cs +++ b/osu.Game.Tournament/TournamentGame.cs @@ -40,7 +40,7 @@ namespace osu.Game.Tournament private Bindable windowMode; private LoadingSpinner loadingSpinner; - protected override int SoftHandledExceptions => DebugUtils.IsDebugBuild ? 0 : 1; + protected override int ExceptionsBeforeCrash => DebugUtils.IsDebugBuild ? 0 : 1; [BackgroundDependencyLoader] private void load(FrameworkConfigManager frameworkConfig, GameHost host) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 6961dce910..0cfb3c2de8 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -176,7 +176,7 @@ namespace osu.Game private readonly List visibleBlockingOverlays = new List(); - protected override int SoftHandledExceptions => DebugUtils.IsDebugBuild ? 0 : 1; + protected override int ExceptionsBeforeCrash => DebugUtils.IsDebugBuild ? 0 : 1; public OsuGame(string[] args = null) { diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 3a35bce05c..b17526114a 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -182,15 +182,20 @@ namespace osu.Game protected DatabaseContextFactory EFContextFactory { get; private set; } /// - /// Number of exceptions to allow before aborting execution. + /// Number of unhandled exceptions to allow before aborting execution. /// - protected virtual int SoftHandledExceptions => 0; + /// + /// When an unhandled exception is encountered, an internal count will be decremented. + /// If the count hits zero, the game will crash. + /// Each second, the count is incremented until reaching the value specified. + /// + protected virtual int ExceptionsBeforeCrash => 0; public OsuGameBase() { Name = @"osu!"; - allowableExceptions = SoftHandledExceptions; + allowableExceptions = ExceptionsBeforeCrash; } [BackgroundDependencyLoader] From b2a57c34bbea091d2e172c03a05c9481e9312bc7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 10 May 2022 19:10:34 +0900 Subject: [PATCH 074/147] Move implementation to base --- osu.Game.Tournament/TournamentGame.cs | 3 --- osu.Game/OsuGame.cs | 3 --- osu.Game/OsuGameBase.cs | 4 ++-- 3 files changed, 2 insertions(+), 8 deletions(-) diff --git a/osu.Game.Tournament/TournamentGame.cs b/osu.Game.Tournament/TournamentGame.cs index 042e660122..7967f54b49 100644 --- a/osu.Game.Tournament/TournamentGame.cs +++ b/osu.Game.Tournament/TournamentGame.cs @@ -6,7 +6,6 @@ using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Configuration; -using osu.Framework.Development; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; @@ -40,8 +39,6 @@ namespace osu.Game.Tournament private Bindable windowMode; private LoadingSpinner loadingSpinner; - protected override int ExceptionsBeforeCrash => DebugUtils.IsDebugBuild ? 0 : 1; - [BackgroundDependencyLoader] private void load(FrameworkConfigManager frameworkConfig, GameHost host) { diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 0cfb3c2de8..54c4231b06 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -13,7 +13,6 @@ using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Bindables; using osu.Framework.Configuration; -using osu.Framework.Development; using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -176,8 +175,6 @@ namespace osu.Game private readonly List visibleBlockingOverlays = new List(); - protected override int ExceptionsBeforeCrash => DebugUtils.IsDebugBuild ? 0 : 1; - public OsuGame(string[] args = null) { this.args = args; diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index b17526114a..ce798d4027 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -189,13 +189,13 @@ namespace osu.Game /// If the count hits zero, the game will crash. /// Each second, the count is incremented until reaching the value specified. /// - protected virtual int ExceptionsBeforeCrash => 0; + protected virtual int UnhandledExceptionsBeforeCrash => DebugUtils.IsDebugBuild ? 0 : 1; public OsuGameBase() { Name = @"osu!"; - allowableExceptions = ExceptionsBeforeCrash; + allowableExceptions = UnhandledExceptionsBeforeCrash; } [BackgroundDependencyLoader] From 42fe7082250654233c6c78074a61cfbfec0866fb Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 10 May 2022 19:56:21 +0900 Subject: [PATCH 075/147] Fix inspection --- osu.Game/Overlays/FirstRunSetupOverlay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/FirstRunSetupOverlay.cs b/osu.Game/Overlays/FirstRunSetupOverlay.cs index c0eed14c99..cebb2f5e3b 100644 --- a/osu.Game/Overlays/FirstRunSetupOverlay.cs +++ b/osu.Game/Overlays/FirstRunSetupOverlay.cs @@ -91,7 +91,7 @@ namespace osu.Game.Overlays Origin = Anchor.Centre, RelativeSizeAxes = Axes.Both, Padding = new MarginPadding { Bottom = 20, }, - Child = new GridContainer() + Child = new GridContainer { Anchor = Anchor.Centre, Origin = Anchor.Centre, From 87f6f74795beb592dd50e5994f2f48bca590eac6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 10 May 2022 21:03:55 +0900 Subject: [PATCH 076/147] Add failing test coverage of adding a file to a detached beatmap across threads --- .../Database/BeatmapImporterTests.cs | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/osu.Game.Tests/Database/BeatmapImporterTests.cs b/osu.Game.Tests/Database/BeatmapImporterTests.cs index f9c13a8169..b7bfe14402 100644 --- a/osu.Game.Tests/Database/BeatmapImporterTests.cs +++ b/osu.Game.Tests/Database/BeatmapImporterTests.cs @@ -136,6 +136,37 @@ namespace osu.Game.Tests.Database }); } + [Test] + public void TestAddFileToAsyncImportedBeatmap() + { + RunTestWithRealm((realm, storage) => + { + BeatmapSetInfo? detachedSet = null; + + using (var importer = new BeatmapModelManager(realm, storage)) + using (new RealmRulesetStore(realm, storage)) + { + Task.Run(async () => + { + Live? beatmapSet; + + using (var reader = new ZipArchiveReader(TestResources.GetTestBeatmapStream())) + // ReSharper disable once AccessToDisposedClosure + beatmapSet = await importer.Import(reader); + + Assert.NotNull(beatmapSet); + Debug.Assert(beatmapSet != null); + + // Intentionally detach on async thread as to not trigger a refresh on the main thread. + beatmapSet.PerformRead(s => detachedSet = s.Detach()); + }).WaitSafely(); + + Debug.Assert(detachedSet != null); + importer.AddFile(detachedSet, new MemoryStream(), "test"); + } + }); + } + [Test] public void TestImportBeatmapThenCleanup() { From 33f024212ffaaa1d70c38b20ff46e42b77fd8654 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 10 May 2022 20:47:26 +0900 Subject: [PATCH 077/147] Fix realm refetch operations potentially being unsafe As seen in test failure https://github.com/ppy/osu/runs/6357384721?check_suite_focus=true. --- osu.Game/Database/RealmAccess.cs | 20 +++++++++++++++++ osu.Game/Stores/RealmArchiveModelManager.cs | 25 ++++++++++++++------- 2 files changed, 37 insertions(+), 8 deletions(-) diff --git a/osu.Game/Database/RealmAccess.cs b/osu.Game/Database/RealmAccess.cs index b0a70b51d0..937876a70e 100644 --- a/osu.Game/Database/RealmAccess.cs +++ b/osu.Game/Database/RealmAccess.cs @@ -344,6 +344,26 @@ namespace osu.Game.Database } } + /// + /// Write changes to realm. + /// + /// The work to run. + public T Write(Func action) + { + if (ThreadSafety.IsUpdateThread) + { + total_writes_update.Value++; + return Realm.Write(action); + } + else + { + total_writes_async.Value++; + + using (var realm = getRealmInstance()) + return realm.Write(action); + } + } + /// /// Write changes to realm. /// diff --git a/osu.Game/Stores/RealmArchiveModelManager.cs b/osu.Game/Stores/RealmArchiveModelManager.cs index 57e51b79aa..e349efe5ad 100644 --- a/osu.Game/Stores/RealmArchiveModelManager.cs +++ b/osu.Game/Stores/RealmArchiveModelManager.cs @@ -45,11 +45,16 @@ namespace osu.Game.Stores // This method should be removed as soon as all the surrounding pieces support non-detached operations. if (!item.IsManaged) { - var managed = Realm.Realm.Find(item.ID); - managed.Realm.Write(() => operation(managed)); + // Importantly, begin the realm write *before* re-fetching, else the update realm may not be in a consistent state + // (ie. if an async import finished very recently). + Realm.Realm.Write(realm => + { + var managed = Realm.Realm.Find(item.ID); + operation(managed); - item.Files.Clear(); - item.Files.AddRange(managed.Files.Detach()); + item.Files.Clear(); + item.Files.AddRange(managed.Files.Detach()); + }); } else operation(item); @@ -165,7 +170,9 @@ namespace osu.Game.Stores public bool Delete(TModel item) { - return Realm.Run(realm => + // Importantly, begin the realm write *before* re-fetching, else the update realm may not be in a consistent state + // (ie. if an async import finished very recently). + return Realm.Write(realm => { if (!item.IsManaged) item = realm.Find(item.ID); @@ -173,14 +180,16 @@ namespace osu.Game.Stores if (item?.DeletePending != false) return false; - realm.Write(r => item.DeletePending = true); + item.DeletePending = true; return true; }); } public void Undelete(TModel item) { - Realm.Run(realm => + // Importantly, begin the realm write *before* re-fetching, else the update realm may not be in a consistent state + // (ie. if an async import finished very recently). + Realm.Write(realm => { if (!item.IsManaged) item = realm.Find(item.ID); @@ -188,7 +197,7 @@ namespace osu.Game.Stores if (item?.DeletePending != true) return; - realm.Write(r => item.DeletePending = false); + item.DeletePending = false; }); } From 0eb29d56f258a2febc465de501bc22ef219bbecf Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 10 May 2022 19:46:55 +0900 Subject: [PATCH 078/147] Rename new test method to be english --- osu.Game.Tests/Visual/UserInterface/TestSceneModSelectScreen.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectScreen.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectScreen.cs index 661465b484..6e8ed5ebb4 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectScreen.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectScreen.cs @@ -482,7 +482,7 @@ namespace osu.Game.Tests.Visual.UserInterface } [Test] - public void TestCorrectAudioAdjustmentDeapplication() + public void TestCorrectAudioAdjustmentAfterPitchAdjustChange() { createScreen(); changeRuleset(0); From bbbecbb6b7bcf080033b3236dc09918957ef5fb8 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 10 May 2022 17:54:34 +0300 Subject: [PATCH 079/147] Apply time-ramping adjustment using clock instead of track --- osu.Game/Rulesets/Mods/ModTimeRamp.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Mods/ModTimeRamp.cs b/osu.Game/Rulesets/Mods/ModTimeRamp.cs index fe6d54332c..12046d1577 100644 --- a/osu.Game/Rulesets/Mods/ModTimeRamp.cs +++ b/osu.Game/Rulesets/Mods/ModTimeRamp.cs @@ -90,7 +90,7 @@ namespace osu.Game.Rulesets.Mods public virtual void Update(Playfield playfield) { - applyRateAdjustment(track.CurrentTime); + applyRateAdjustment(playfield.Clock.CurrentTime); } /// From 4f5001704e6b45a4b9e4a4a47f1cde91eb92c8be Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 10 May 2022 18:01:15 +0300 Subject: [PATCH 080/147] Change `IApplicableToTrack` to receive adjustable component instead --- osu.Game/Rulesets/Mods/IApplicableToTrack.cs | 4 ++-- osu.Game/Rulesets/Mods/ModAdaptiveSpeed.cs | 6 ++---- osu.Game/Rulesets/Mods/ModDaycore.cs | 3 +-- osu.Game/Rulesets/Mods/ModMuted.cs | 3 +-- osu.Game/Rulesets/Mods/ModNightcore.cs | 2 +- osu.Game/Rulesets/Mods/ModRateAdjust.cs | 3 +-- osu.Game/Rulesets/Mods/ModTimeRamp.cs | 5 ++--- 7 files changed, 10 insertions(+), 16 deletions(-) diff --git a/osu.Game/Rulesets/Mods/IApplicableToTrack.cs b/osu.Game/Rulesets/Mods/IApplicableToTrack.cs index 9b840cea08..deecd4bf1f 100644 --- a/osu.Game/Rulesets/Mods/IApplicableToTrack.cs +++ b/osu.Game/Rulesets/Mods/IApplicableToTrack.cs @@ -1,7 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using osu.Framework.Audio.Track; +using osu.Framework.Audio; namespace osu.Game.Rulesets.Mods { @@ -10,6 +10,6 @@ namespace osu.Game.Rulesets.Mods /// public interface IApplicableToTrack : IApplicableMod { - void ApplyToTrack(ITrack track); + void ApplyToTrack(IAdjustableAudioComponent track); } } diff --git a/osu.Game/Rulesets/Mods/ModAdaptiveSpeed.cs b/osu.Game/Rulesets/Mods/ModAdaptiveSpeed.cs index 93251f7b2d..c9661662bf 100644 --- a/osu.Game/Rulesets/Mods/ModAdaptiveSpeed.cs +++ b/osu.Game/Rulesets/Mods/ModAdaptiveSpeed.cs @@ -5,7 +5,6 @@ using System; using System.Collections.Generic; using System.Linq; using osu.Framework.Audio; -using osu.Framework.Audio.Track; using osu.Framework.Bindables; using osu.Framework.Graphics.Audio; using osu.Framework.Utils; @@ -79,7 +78,7 @@ namespace osu.Game.Rulesets.Mods // Apply a fixed rate change when missing, allowing the player to catch up when the rate is too fast. private const double rate_change_on_miss = 0.95d; - private ITrack track; + private IAdjustableAudioComponent track; private double targetRate = 1d; /// @@ -141,7 +140,7 @@ namespace osu.Game.Rulesets.Mods AdjustPitch.BindValueChanged(adjustPitchChanged); } - public void ApplyToTrack(ITrack track) + public void ApplyToTrack(IAdjustableAudioComponent track) { this.track = track; @@ -210,7 +209,6 @@ namespace osu.Game.Rulesets.Mods private void adjustPitchChanged(ValueChangedEvent adjustPitchSetting) { track?.RemoveAdjustment(adjustmentForPitchSetting(adjustPitchSetting.OldValue), SpeedChange); - track?.AddAdjustment(adjustmentForPitchSetting(adjustPitchSetting.NewValue), SpeedChange); } diff --git a/osu.Game/Rulesets/Mods/ModDaycore.cs b/osu.Game/Rulesets/Mods/ModDaycore.cs index 61ad7db706..9e8e44229e 100644 --- a/osu.Game/Rulesets/Mods/ModDaycore.cs +++ b/osu.Game/Rulesets/Mods/ModDaycore.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Audio; -using osu.Framework.Audio.Track; using osu.Framework.Bindables; using osu.Framework.Graphics.Sprites; @@ -27,7 +26,7 @@ namespace osu.Game.Rulesets.Mods }, true); } - public override void ApplyToTrack(ITrack track) + public override void ApplyToTrack(IAdjustableAudioComponent track) { // base.ApplyToTrack() intentionally not called (different tempo adjustment is applied) track.AddAdjustment(AdjustableProperty.Frequency, freqAdjust); diff --git a/osu.Game/Rulesets/Mods/ModMuted.cs b/osu.Game/Rulesets/Mods/ModMuted.cs index 1d33b44812..a7d3114f2b 100644 --- a/osu.Game/Rulesets/Mods/ModMuted.cs +++ b/osu.Game/Rulesets/Mods/ModMuted.cs @@ -3,7 +3,6 @@ using System.Linq; using osu.Framework.Audio; -using osu.Framework.Audio.Track; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; @@ -71,7 +70,7 @@ namespace osu.Game.Rulesets.Mods InverseMuting.BindValueChanged(i => MuteComboCount.MinValue = i.NewValue ? 1 : 0, true); } - public void ApplyToTrack(ITrack track) + public void ApplyToTrack(IAdjustableAudioComponent track) { track.AddAdjustment(AdjustableProperty.Volume, mainVolumeAdjust); } diff --git a/osu.Game/Rulesets/Mods/ModNightcore.cs b/osu.Game/Rulesets/Mods/ModNightcore.cs index 993efead33..7997204450 100644 --- a/osu.Game/Rulesets/Mods/ModNightcore.cs +++ b/osu.Game/Rulesets/Mods/ModNightcore.cs @@ -41,7 +41,7 @@ namespace osu.Game.Rulesets.Mods }, true); } - public override void ApplyToTrack(ITrack track) + public override void ApplyToTrack(IAdjustableAudioComponent track) { // base.ApplyToTrack() intentionally not called (different tempo adjustment is applied) track.AddAdjustment(AdjustableProperty.Frequency, freqAdjust); diff --git a/osu.Game/Rulesets/Mods/ModRateAdjust.cs b/osu.Game/Rulesets/Mods/ModRateAdjust.cs index 05953f903f..49590c30ca 100644 --- a/osu.Game/Rulesets/Mods/ModRateAdjust.cs +++ b/osu.Game/Rulesets/Mods/ModRateAdjust.cs @@ -3,7 +3,6 @@ using System; using osu.Framework.Audio; -using osu.Framework.Audio.Track; using osu.Framework.Bindables; using osu.Framework.Graphics.Audio; @@ -15,7 +14,7 @@ namespace osu.Game.Rulesets.Mods public abstract BindableNumber SpeedChange { get; } - public virtual void ApplyToTrack(ITrack track) + public virtual void ApplyToTrack(IAdjustableAudioComponent track) { track.AddAdjustment(AdjustableProperty.Tempo, SpeedChange); } diff --git a/osu.Game/Rulesets/Mods/ModTimeRamp.cs b/osu.Game/Rulesets/Mods/ModTimeRamp.cs index 12046d1577..22d6d4ca29 100644 --- a/osu.Game/Rulesets/Mods/ModTimeRamp.cs +++ b/osu.Game/Rulesets/Mods/ModTimeRamp.cs @@ -4,7 +4,6 @@ using System; using System.Linq; using osu.Framework.Audio; -using osu.Framework.Audio.Track; using osu.Framework.Bindables; using osu.Framework.Graphics.Audio; using osu.Game.Beatmaps; @@ -46,7 +45,7 @@ namespace osu.Game.Rulesets.Mods Precision = 0.01, }; - private ITrack track; + private IAdjustableAudioComponent track; protected ModTimeRamp() { @@ -55,7 +54,7 @@ namespace osu.Game.Rulesets.Mods AdjustPitch.BindValueChanged(applyPitchAdjustment); } - public void ApplyToTrack(ITrack track) + public void ApplyToTrack(IAdjustableAudioComponent track) { this.track = track; From 82b784ce5a2b29839ea3b4177c1274bd31eec2d4 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 10 May 2022 18:01:36 +0300 Subject: [PATCH 081/147] Change `IApplicableToSample` to receive adjustable component instead Done for consistency with `IApplicableToTrack`. --- osu.Game/Rulesets/Mods/IApplicableToSample.cs | 4 ++-- osu.Game/Rulesets/Mods/ModAdaptiveSpeed.cs | 3 +-- osu.Game/Rulesets/Mods/ModRateAdjust.cs | 3 +-- osu.Game/Rulesets/Mods/ModTimeRamp.cs | 3 +-- 4 files changed, 5 insertions(+), 8 deletions(-) diff --git a/osu.Game/Rulesets/Mods/IApplicableToSample.cs b/osu.Game/Rulesets/Mods/IApplicableToSample.cs index 50a6d501b6..efd88f2399 100644 --- a/osu.Game/Rulesets/Mods/IApplicableToSample.cs +++ b/osu.Game/Rulesets/Mods/IApplicableToSample.cs @@ -1,7 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using osu.Framework.Graphics.Audio; +using osu.Framework.Audio; namespace osu.Game.Rulesets.Mods { @@ -10,6 +10,6 @@ namespace osu.Game.Rulesets.Mods /// public interface IApplicableToSample : IApplicableMod { - void ApplyToSample(DrawableSample sample); + void ApplyToSample(IAdjustableAudioComponent sample); } } diff --git a/osu.Game/Rulesets/Mods/ModAdaptiveSpeed.cs b/osu.Game/Rulesets/Mods/ModAdaptiveSpeed.cs index c9661662bf..fb291fe10f 100644 --- a/osu.Game/Rulesets/Mods/ModAdaptiveSpeed.cs +++ b/osu.Game/Rulesets/Mods/ModAdaptiveSpeed.cs @@ -6,7 +6,6 @@ using System.Collections.Generic; using System.Linq; using osu.Framework.Audio; using osu.Framework.Bindables; -using osu.Framework.Graphics.Audio; using osu.Framework.Utils; using osu.Game.Beatmaps; using osu.Game.Configuration; @@ -150,7 +149,7 @@ namespace osu.Game.Rulesets.Mods recentRates.AddRange(Enumerable.Repeat(InitialRate.Value, recent_rate_count)); } - public void ApplyToSample(DrawableSample sample) + public void ApplyToSample(IAdjustableAudioComponent sample) { sample.AddAdjustment(AdjustableProperty.Frequency, SpeedChange); } diff --git a/osu.Game/Rulesets/Mods/ModRateAdjust.cs b/osu.Game/Rulesets/Mods/ModRateAdjust.cs index 49590c30ca..7b55ba4ad0 100644 --- a/osu.Game/Rulesets/Mods/ModRateAdjust.cs +++ b/osu.Game/Rulesets/Mods/ModRateAdjust.cs @@ -4,7 +4,6 @@ using System; using osu.Framework.Audio; using osu.Framework.Bindables; -using osu.Framework.Graphics.Audio; namespace osu.Game.Rulesets.Mods { @@ -19,7 +18,7 @@ namespace osu.Game.Rulesets.Mods track.AddAdjustment(AdjustableProperty.Tempo, SpeedChange); } - public virtual void ApplyToSample(DrawableSample sample) + public virtual void ApplyToSample(IAdjustableAudioComponent sample) { sample.AddAdjustment(AdjustableProperty.Frequency, SpeedChange); } diff --git a/osu.Game/Rulesets/Mods/ModTimeRamp.cs b/osu.Game/Rulesets/Mods/ModTimeRamp.cs index 22d6d4ca29..98abda872b 100644 --- a/osu.Game/Rulesets/Mods/ModTimeRamp.cs +++ b/osu.Game/Rulesets/Mods/ModTimeRamp.cs @@ -5,7 +5,6 @@ using System; using System.Linq; using osu.Framework.Audio; using osu.Framework.Bindables; -using osu.Framework.Graphics.Audio; using osu.Game.Beatmaps; using osu.Game.Configuration; using osu.Game.Rulesets.Objects; @@ -62,7 +61,7 @@ namespace osu.Game.Rulesets.Mods AdjustPitch.TriggerChange(); } - public void ApplyToSample(DrawableSample sample) + public void ApplyToSample(IAdjustableAudioComponent sample) { sample.AddAdjustment(AdjustableProperty.Frequency, SpeedChange); } From 725ff93f34e9b49cca90e1006089d512e9891f06 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 10 May 2022 18:02:32 +0300 Subject: [PATCH 082/147] Define local adjustments component for mods in `MusicController` Isolates `CurrentTrack` from being directly adjusted by the mod, which could lead to issues depending on how the mod adds adjustments (i.e. `ModTimeRamp`, which adds adjustments based on changes to a setting bindable). --- osu.Game/Overlays/MusicController.cs | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/osu.Game/Overlays/MusicController.cs b/osu.Game/Overlays/MusicController.cs index 5fc0da8891..0373856ace 100644 --- a/osu.Game/Overlays/MusicController.cs +++ b/osu.Game/Overlays/MusicController.cs @@ -377,6 +377,8 @@ namespace osu.Game.Overlays } } + private readonly AudioAdjustments modTrackAdjustments = new AudioAdjustments(); + /// /// Resets the adjustments currently applied on and applies the mod adjustments if is true. /// @@ -385,15 +387,27 @@ namespace osu.Game.Overlays /// public void ResetTrackAdjustments() { + // todo: we probably want a helper method rather than this. CurrentTrack.RemoveAllAdjustments(AdjustableProperty.Balance); CurrentTrack.RemoveAllAdjustments(AdjustableProperty.Frequency); CurrentTrack.RemoveAllAdjustments(AdjustableProperty.Tempo); CurrentTrack.RemoveAllAdjustments(AdjustableProperty.Volume); - if (allowTrackAdjustments) + modTrackAdjustments.RemoveAllAdjustments(AdjustableProperty.Balance); + modTrackAdjustments.RemoveAllAdjustments(AdjustableProperty.Frequency); + modTrackAdjustments.RemoveAllAdjustments(AdjustableProperty.Tempo); + modTrackAdjustments.RemoveAllAdjustments(AdjustableProperty.Volume); + + var applicableToTrack = mods.Value.OfType(); + + if (!allowTrackAdjustments || !applicableToTrack.Any()) + CurrentTrack.UnbindAdjustments(modTrackAdjustments); + else { - foreach (var mod in mods.Value.OfType()) - mod.ApplyToTrack(CurrentTrack); + CurrentTrack.BindAdjustments(modTrackAdjustments); + + foreach (var mod in applicableToTrack) + mod.ApplyToTrack(modTrackAdjustments); } } } From ec231e0f312eeeecb8229a0fe1ee0660935b455f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 11 May 2022 00:45:17 +0900 Subject: [PATCH 083/147] Use more local realm reference MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Bartłomiej Dach --- osu.Game/Stores/RealmArchiveModelManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Stores/RealmArchiveModelManager.cs b/osu.Game/Stores/RealmArchiveModelManager.cs index e349efe5ad..cc8229b436 100644 --- a/osu.Game/Stores/RealmArchiveModelManager.cs +++ b/osu.Game/Stores/RealmArchiveModelManager.cs @@ -49,7 +49,7 @@ namespace osu.Game.Stores // (ie. if an async import finished very recently). Realm.Realm.Write(realm => { - var managed = Realm.Realm.Find(item.ID); + var managed = realm.Find(item.ID); operation(managed); item.Files.Clear(); From a0f1c48e806ff64f6c58b521b43c1d87f3d83e0b Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 10 May 2022 19:24:36 +0300 Subject: [PATCH 084/147] Fix `ModTimeRampTest` failing due to changes in `Update` method --- osu.Game.Tests/Rulesets/Mods/ModTimeRampTest.cs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Rulesets/Mods/ModTimeRampTest.cs b/osu.Game.Tests/Rulesets/Mods/ModTimeRampTest.cs index 4b9f2181dc..51163efd6a 100644 --- a/osu.Game.Tests/Rulesets/Mods/ModTimeRampTest.cs +++ b/osu.Game.Tests/Rulesets/Mods/ModTimeRampTest.cs @@ -3,9 +3,11 @@ using NUnit.Framework; using osu.Framework.Audio.Track; +using osu.Framework.Timing; using osu.Game.Beatmaps; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Osu.UI; namespace osu.Game.Tests.Rulesets.Mods { @@ -16,11 +18,14 @@ namespace osu.Game.Tests.Rulesets.Mods private const double duration = 9000; private TrackVirtual track; + private OsuPlayfield playfield; [SetUp] public void SetUp() { track = new TrackVirtual(20_000); + // define a fake playfield to re-calculate the current rate by ModTimeRamp.Update(Playfield). + playfield = new OsuPlayfield { Clock = new FramedClock(track) }; } [TestCase(0, 1)] @@ -80,8 +85,8 @@ namespace osu.Game.Tests.Rulesets.Mods private void seekTrackAndUpdateMod(ModTimeRamp mod, double time) { track.Seek(time); - // update the mod via a fake playfield to re-calculate the current rate. - mod.Update(null); + playfield.Clock.ProcessFrame(); + mod.Update(playfield); } private static Beatmap createSingleSpinnerBeatmap() From 36a764416400d2d8f77793a1e258f5f7d092742a Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 10 May 2022 20:46:31 +0300 Subject: [PATCH 085/147] Reinstantiate mod adjustments layer for safety against previous mods --- osu.Game/Overlays/MusicController.cs | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/osu.Game/Overlays/MusicController.cs b/osu.Game/Overlays/MusicController.cs index 0373856ace..50495d0a2c 100644 --- a/osu.Game/Overlays/MusicController.cs +++ b/osu.Game/Overlays/MusicController.cs @@ -377,7 +377,7 @@ namespace osu.Game.Overlays } } - private readonly AudioAdjustments modTrackAdjustments = new AudioAdjustments(); + private AudioAdjustments modTrackAdjustments; /// /// Resets the adjustments currently applied on and applies the mod adjustments if is true. @@ -393,18 +393,19 @@ namespace osu.Game.Overlays CurrentTrack.RemoveAllAdjustments(AdjustableProperty.Tempo); CurrentTrack.RemoveAllAdjustments(AdjustableProperty.Volume); - modTrackAdjustments.RemoveAllAdjustments(AdjustableProperty.Balance); - modTrackAdjustments.RemoveAllAdjustments(AdjustableProperty.Frequency); - modTrackAdjustments.RemoveAllAdjustments(AdjustableProperty.Tempo); - modTrackAdjustments.RemoveAllAdjustments(AdjustableProperty.Volume); - var applicableToTrack = mods.Value.OfType(); if (!allowTrackAdjustments || !applicableToTrack.Any()) - CurrentTrack.UnbindAdjustments(modTrackAdjustments); + { + if (modTrackAdjustments != null) + { + CurrentTrack.UnbindAdjustments(modTrackAdjustments); + modTrackAdjustments = null; + } + } else { - CurrentTrack.BindAdjustments(modTrackAdjustments); + CurrentTrack.BindAdjustments(modTrackAdjustments = new AudioAdjustments()); foreach (var mod in applicableToTrack) mod.ApplyToTrack(modTrackAdjustments); From 9446be251117f6b904c67b09aea0155b535738a6 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 10 May 2022 20:49:15 +0300 Subject: [PATCH 086/147] Remove unnecessary `UnbindAdjustments` call It is not necessary given that `CurrentTrack` already removes all adjustments first. --- osu.Game/Overlays/MusicController.cs | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/osu.Game/Overlays/MusicController.cs b/osu.Game/Overlays/MusicController.cs index 50495d0a2c..65b06eb864 100644 --- a/osu.Game/Overlays/MusicController.cs +++ b/osu.Game/Overlays/MusicController.cs @@ -393,21 +393,11 @@ namespace osu.Game.Overlays CurrentTrack.RemoveAllAdjustments(AdjustableProperty.Tempo); CurrentTrack.RemoveAllAdjustments(AdjustableProperty.Volume); - var applicableToTrack = mods.Value.OfType(); - - if (!allowTrackAdjustments || !applicableToTrack.Any()) - { - if (modTrackAdjustments != null) - { - CurrentTrack.UnbindAdjustments(modTrackAdjustments); - modTrackAdjustments = null; - } - } - else + if (allowTrackAdjustments) { CurrentTrack.BindAdjustments(modTrackAdjustments = new AudioAdjustments()); - foreach (var mod in applicableToTrack) + foreach (var mod in mods.Value.OfType()) mod.ApplyToTrack(modTrackAdjustments); } } From 9cfe2cc310b916f2ca78ca1daefd256a777f2dab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 10 May 2022 21:43:57 +0200 Subject: [PATCH 087/147] Move `TestCustomisableModRuleset` out of `TestSceneModSettings` --- .../Mods/TestCustomisableModRuleset.cs | 80 +++++++++++++++++++ .../Gameplay/TestSceneReplayRecorder.cs | 6 +- .../Gameplay/TestSceneSpectatorPlayback.cs | 6 +- .../UserInterface/TestSceneModSettings.cs | 78 +----------------- 4 files changed, 90 insertions(+), 80 deletions(-) create mode 100644 osu.Game.Tests/Mods/TestCustomisableModRuleset.cs diff --git a/osu.Game.Tests/Mods/TestCustomisableModRuleset.cs b/osu.Game.Tests/Mods/TestCustomisableModRuleset.cs new file mode 100644 index 0000000000..3992d9abe6 --- /dev/null +++ b/osu.Game.Tests/Mods/TestCustomisableModRuleset.cs @@ -0,0 +1,80 @@ +// 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 osu.Framework.Bindables; +using osu.Game.Beatmaps; +using osu.Game.Configuration; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Difficulty; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.UI; + +namespace osu.Game.Tests.Mods +{ + public class TestCustomisableModRuleset : Ruleset + { + public static RulesetInfo CreateTestRulesetInfo() => new TestCustomisableModRuleset().RulesetInfo; + + public override IEnumerable GetModsFor(ModType type) + { + if (type == ModType.Conversion) + { + return new Mod[] + { + new TestModCustomisable1(), + new TestModCustomisable2() + }; + } + + return Array.Empty(); + } + + public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList mods = null) => throw new NotImplementedException(); + + public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => throw new NotImplementedException(); + + public override DifficultyCalculator CreateDifficultyCalculator(IWorkingBeatmap beatmap) => throw new NotImplementedException(); + + public override string Description { get; } = "test"; + public override string ShortName { get; } = "tst"; + + public class TestModCustomisable1 : TestModCustomisable + { + public override string Name => "Customisable Mod 1"; + + public override string Acronym => "CM1"; + } + + public class TestModCustomisable2 : TestModCustomisable + { + public override string Name => "Customisable Mod 2"; + + public override string Acronym => "CM2"; + + public override bool RequiresConfiguration => true; + } + + public abstract class TestModCustomisable : Mod, IApplicableMod + { + public override double ScoreMultiplier => 1.0; + + public override string Description => "This is a customisable test mod."; + + public override ModType Type => ModType.Conversion; + + [SettingSource("Sample float", "Change something for a mod")] + public BindableFloat SliderBindable { get; } = new BindableFloat + { + MinValue = 0, + MaxValue = 10, + Default = 5, + Value = 7 + }; + + [SettingSource("Sample bool", "Clicking this changes a setting")] + public BindableBool TickBindable { get; } = new BindableBool(); + } + } +} diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecorder.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecorder.cs index 8df32c500e..81763564fa 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecorder.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecorder.cs @@ -24,7 +24,7 @@ using osu.Game.Rulesets.Replays; using osu.Game.Rulesets.UI; using osu.Game.Scoring; using osu.Game.Screens.Play; -using osu.Game.Tests.Visual.UserInterface; +using osu.Game.Tests.Mods; using osuTK; using osuTK.Graphics; using osuTK.Input; @@ -59,7 +59,7 @@ namespace osu.Game.Tests.Visual.Gameplay { new Drawable[] { - recordingManager = new TestRulesetInputManager(TestSceneModSettings.CreateTestRulesetInfo(), 0, SimultaneousBindingMode.Unique) + recordingManager = new TestRulesetInputManager(TestCustomisableModRuleset.CreateTestRulesetInfo(), 0, SimultaneousBindingMode.Unique) { Recorder = recorder = new TestReplayRecorder(new Score { @@ -97,7 +97,7 @@ namespace osu.Game.Tests.Visual.Gameplay }, new Drawable[] { - playbackManager = new TestRulesetInputManager(TestSceneModSettings.CreateTestRulesetInfo(), 0, SimultaneousBindingMode.Unique) + playbackManager = new TestRulesetInputManager(TestCustomisableModRuleset.CreateTestRulesetInfo(), 0, SimultaneousBindingMode.Unique) { ReplayInputHandler = new TestFramedReplayInputHandler(replay) { diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs index 4ec46036f6..f8748922cf 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs @@ -27,8 +27,8 @@ using osu.Game.Rulesets.Replays.Types; using osu.Game.Rulesets.UI; using osu.Game.Scoring; using osu.Game.Screens.Play; +using osu.Game.Tests.Mods; using osu.Game.Tests.Visual.Spectator; -using osu.Game.Tests.Visual.UserInterface; using osuTK; using osuTK.Graphics; @@ -77,7 +77,7 @@ namespace osu.Game.Tests.Visual.Gameplay { new Drawable[] { - recordingManager = new TestRulesetInputManager(TestSceneModSettings.CreateTestRulesetInfo(), 0, SimultaneousBindingMode.Unique) + recordingManager = new TestRulesetInputManager(TestCustomisableModRuleset.CreateTestRulesetInfo(), 0, SimultaneousBindingMode.Unique) { Recorder = recorder = new TestReplayRecorder { @@ -107,7 +107,7 @@ namespace osu.Game.Tests.Visual.Gameplay }, new Drawable[] { - playbackManager = new TestRulesetInputManager(TestSceneModSettings.CreateTestRulesetInfo(), 0, SimultaneousBindingMode.Unique) + playbackManager = new TestRulesetInputManager(TestCustomisableModRuleset.CreateTestRulesetInfo(), 0, SimultaneousBindingMode.Unique) { Clock = new FramedClock(manualClock), ReplayInputHandler = replayHandler = new TestFramedReplayInputHandler(replay) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModSettings.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModSettings.cs index 9a3083e8db..379735a7f5 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModSettings.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModSettings.cs @@ -5,20 +5,15 @@ using System; using System.Collections.Generic; using System.Linq; using NUnit.Framework; -using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Utils; using osu.Framework.Testing; -using osu.Game.Beatmaps; -using osu.Game.Configuration; using osu.Game.Graphics.UserInterface; using osu.Game.Overlays.Mods; -using osu.Game.Rulesets; -using osu.Game.Rulesets.Difficulty; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu.Mods; -using osu.Game.Rulesets.UI; +using osu.Game.Tests.Mods; using osuTK.Input; namespace osu.Game.Tests.Visual.UserInterface @@ -27,15 +22,15 @@ namespace osu.Game.Tests.Visual.UserInterface { private TestModSelectOverlay modSelect; - private readonly Mod testCustomisableMod = new TestModCustomisable1(); + private readonly Mod testCustomisableMod = new TestCustomisableModRuleset.TestModCustomisable1(); - private readonly Mod testCustomisableAutoOpenMod = new TestModCustomisable2(); + private readonly Mod testCustomisableAutoOpenMod = new TestCustomisableModRuleset.TestModCustomisable2(); [SetUp] public void SetUp() => Schedule(() => { SelectedMods.Value = Array.Empty(); - Ruleset.Value = CreateTestRulesetInfo(); + Ruleset.Value = TestCustomisableModRuleset.CreateTestRulesetInfo(); }); [Test] @@ -169,70 +164,5 @@ namespace osu.Game.Tests.Visual.UserInterface public void SetModSettingsWidth(float newWidth) => ModSettingsContainer.Parent.Width = newWidth; } - - public static RulesetInfo CreateTestRulesetInfo() => new TestCustomisableModRuleset().RulesetInfo; - - public class TestCustomisableModRuleset : Ruleset - { - public override IEnumerable GetModsFor(ModType type) - { - if (type == ModType.Conversion) - { - return new Mod[] - { - new TestModCustomisable1(), - new TestModCustomisable2() - }; - } - - return Array.Empty(); - } - - public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList mods = null) => throw new NotImplementedException(); - - public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => throw new NotImplementedException(); - - public override DifficultyCalculator CreateDifficultyCalculator(IWorkingBeatmap beatmap) => throw new NotImplementedException(); - - public override string Description { get; } = "test"; - public override string ShortName { get; } = "tst"; - } - - private class TestModCustomisable1 : TestModCustomisable - { - public override string Name => "Customisable Mod 1"; - - public override string Acronym => "CM1"; - } - - private class TestModCustomisable2 : TestModCustomisable - { - public override string Name => "Customisable Mod 2"; - - public override string Acronym => "CM2"; - - public override bool RequiresConfiguration => true; - } - - private abstract class TestModCustomisable : Mod, IApplicableMod - { - public override double ScoreMultiplier => 1.0; - - public override string Description => "This is a customisable test mod."; - - public override ModType Type => ModType.Conversion; - - [SettingSource("Sample float", "Change something for a mod")] - public BindableFloat SliderBindable { get; } = new BindableFloat - { - MinValue = 0, - MaxValue = 10, - Default = 5, - Value = 7 - }; - - [SettingSource("Sample bool", "Clicking this changes a setting")] - public BindableBool TickBindable { get; } = new BindableBool(); - } } } From 4a3447f59f69af6b591f4c6c44f5b3422b229b58 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 10 May 2022 21:45:57 +0200 Subject: [PATCH 088/147] Remove old free mod select overlay --- .../TestSceneFreeModSelectOverlay.cs | 21 --- .../OnlinePlay/FreeModSelectOverlay.cs | 157 ------------------ 2 files changed, 178 deletions(-) delete mode 100644 osu.Game.Tests/Visual/Multiplayer/TestSceneFreeModSelectOverlay.cs delete mode 100644 osu.Game/Screens/OnlinePlay/FreeModSelectOverlay.cs diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneFreeModSelectOverlay.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneFreeModSelectOverlay.cs deleted file mode 100644 index 26a0301d8a..0000000000 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneFreeModSelectOverlay.cs +++ /dev/null @@ -1,21 +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 NUnit.Framework; -using osu.Framework.Graphics.Containers; -using osu.Game.Screens.OnlinePlay; - -namespace osu.Game.Tests.Visual.Multiplayer -{ - public class TestSceneFreeModSelectOverlay : MultiplayerTestScene - { - [SetUp] - public new void Setup() => Schedule(() => - { - Child = new FreeModSelectOverlay - { - State = { Value = Visibility.Visible } - }; - }); - } -} diff --git a/osu.Game/Screens/OnlinePlay/FreeModSelectOverlay.cs b/osu.Game/Screens/OnlinePlay/FreeModSelectOverlay.cs deleted file mode 100644 index d5abaaab4e..0000000000 --- a/osu.Game/Screens/OnlinePlay/FreeModSelectOverlay.cs +++ /dev/null @@ -1,157 +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.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Sprites; -using osu.Game.Graphics; -using osu.Game.Graphics.UserInterface; -using osu.Game.Overlays.Mods; -using osu.Game.Rulesets.Mods; - -namespace osu.Game.Screens.OnlinePlay -{ - /// - /// A used for free-mod selection in online play. - /// - public class FreeModSelectOverlay : ModSelectOverlay - { - protected override bool Stacked => false; - - protected override bool AllowConfiguration => false; - - public new Func IsValidMod - { - get => base.IsValidMod; - set => base.IsValidMod = m => m.HasImplementation && m.UserPlayable && value(m); - } - - public FreeModSelectOverlay() - { - IsValidMod = m => true; - - DeselectAllButton.Alpha = 0; - - Drawable selectAllButton; - Drawable deselectAllButton; - - FooterContainer.AddRange(new[] - { - selectAllButton = new TriangleButton - { - Origin = Anchor.CentreLeft, - Anchor = Anchor.CentreLeft, - Width = 180, - Text = "Select All", - Action = selectAll, - }, - // Unlike the base mod select overlay, this button deselects mods instantaneously. - deselectAllButton = new TriangleButton - { - Origin = Anchor.CentreLeft, - Anchor = Anchor.CentreLeft, - Width = 180, - Text = "Deselect All", - Action = deselectAll, - }, - }); - - FooterContainer.SetLayoutPosition(selectAllButton, -2); - FooterContainer.SetLayoutPosition(deselectAllButton, -1); - } - - private void selectAll() - { - foreach (var section in ModSectionsContainer.Children) - section.SelectAll(); - } - - private void deselectAll() - { - foreach (var section in ModSectionsContainer.Children) - section.DeselectAll(); - } - - protected override void OnAvailableModsChanged() - { - base.OnAvailableModsChanged(); - - foreach (var section in ModSectionsContainer.Children) - ((FreeModSection)section).UpdateCheckboxState(); - } - - protected override ModSection CreateModSection(ModType type) => new FreeModSection(type); - - private class FreeModSection : ModSection - { - private HeaderCheckbox checkbox; - - public FreeModSection(ModType type) - : base(type) - { - } - - protected override Drawable CreateHeader(string text) => new Container - { - AutoSizeAxes = Axes.Y, - RelativeSizeAxes = Axes.X, - Child = checkbox = new HeaderCheckbox - { - LabelText = text, - Changed = onCheckboxChanged - } - }; - - private void onCheckboxChanged(bool value) - { - if (value) - SelectAll(); - else - DeselectAll(); - } - - protected override void ModButtonStateChanged(Mod mod) - { - base.ModButtonStateChanged(mod); - UpdateCheckboxState(); - } - - public void UpdateCheckboxState() - { - if (!SelectionAnimationRunning) - { - var validButtons = Buttons.Where(b => b.Mod.HasImplementation); - checkbox.Current.Value = validButtons.All(b => b.Selected); - } - } - } - - private class HeaderCheckbox : OsuCheckbox - { - public Action Changed; - - protected override bool PlaySoundsOnUserChange => false; - - public HeaderCheckbox() - : base(false) - - { - } - - protected override void ApplyLabelParameters(SpriteText text) - { - base.ApplyLabelParameters(text); - - text.Font = OsuFont.GetFont(weight: FontWeight.Bold); - } - - protected override void OnUserChange(bool value) - { - base.OnUserChange(value); - Changed?.Invoke(value); - } - } - } -} From 24c59e2f2fa942769843ebd8afd299c4008c3c85 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 10 May 2022 21:48:21 +0200 Subject: [PATCH 089/147] Remove old user mod select overlay --- .../TestSceneModSelectOverlay.cs | 472 ------------------ .../UserInterface/TestSceneModSettings.cs | 119 ----- .../IncompatibilityDisplayingModButton.cs | 66 --- .../Overlays/Mods/UserModSelectOverlay.cs | 30 -- 4 files changed, 687 deletions(-) delete mode 100644 osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs delete mode 100644 osu.Game/Overlays/Mods/IncompatibilityDisplayingModButton.cs delete mode 100644 osu.Game/Overlays/Mods/UserModSelectOverlay.cs diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs deleted file mode 100644 index b429619044..0000000000 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs +++ /dev/null @@ -1,472 +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 NUnit.Framework; -using osu.Framework.Allocation; -using osu.Framework.Bindables; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Testing; -using osu.Game.Graphics.UserInterface; -using osu.Game.Overlays.Mods; -using osu.Game.Overlays.Settings; -using osu.Game.Rulesets; -using osu.Game.Rulesets.Mania; -using osu.Game.Rulesets.Mania.Mods; -using osu.Game.Rulesets.Mods; -using osu.Game.Rulesets.Osu; -using osu.Game.Rulesets.Osu.Mods; -using osu.Game.Screens.Play.HUD; -using osuTK; -using osuTK.Graphics; - -namespace osu.Game.Tests.Visual.UserInterface -{ - [Description("mod select and icon display")] - public class TestSceneModSelectOverlay : OsuTestScene - { - private RulesetStore rulesets; - private ModDisplay modDisplay; - private TestModSelectOverlay modSelect; - - [BackgroundDependencyLoader] - private void load(RulesetStore rulesets) - { - this.rulesets = rulesets; - } - - [SetUp] - public void SetUp() => Schedule(() => - { - SelectedMods.Value = Array.Empty(); - createDisplay(() => new TestModSelectOverlay()); - }); - - [SetUpSteps] - public void SetUpSteps() - { - AddStep("show", () => modSelect.Show()); - } - - /// - /// Ensure that two mod overlays are not cross polluting via central settings instances. - /// - [Test] - public void TestSettingsNotCrossPolluting() - { - Bindable> selectedMods2 = null; - - AddStep("select diff adjust", () => SelectedMods.Value = new Mod[] { new OsuModDifficultyAdjust() }); - - AddStep("set setting", () => modSelect.ChildrenOfType>().First().Current.Value = 8); - - AddAssert("ensure setting is propagated", () => SelectedMods.Value.OfType().Single().CircleSize.Value == 8); - - AddStep("create second bindable", () => selectedMods2 = new Bindable>(new Mod[] { new OsuModDifficultyAdjust() })); - - AddStep("create second overlay", () => - { - Add(modSelect = new TestModSelectOverlay().With(d => - { - d.Origin = Anchor.TopCentre; - d.Anchor = Anchor.TopCentre; - d.SelectedMods.BindTarget = selectedMods2; - })); - }); - - AddStep("show", () => modSelect.Show()); - - AddAssert("ensure first is unchanged", () => SelectedMods.Value.OfType().Single().CircleSize.Value == 8); - AddAssert("ensure second is default", () => selectedMods2.Value.OfType().Single().CircleSize.Value == null); - } - - [Test] - public void TestSettingsResetOnDeselection() - { - var osuModDoubleTime = new OsuModDoubleTime { SpeedChange = { Value = 1.2 } }; - - changeRuleset(0); - - AddStep("set dt mod with custom rate", () => { SelectedMods.Value = new[] { osuModDoubleTime }; }); - - AddAssert("selected mod matches", () => (SelectedMods.Value.Single() as OsuModDoubleTime)?.SpeedChange.Value == 1.2); - - AddStep("deselect", () => modSelect.DeselectAllButton.TriggerClick()); - AddAssert("selected mods empty", () => SelectedMods.Value.Count == 0); - - AddStep("reselect", () => modSelect.GetModButton(osuModDoubleTime).TriggerClick()); - AddAssert("selected mod has default value", () => (SelectedMods.Value.Single() as OsuModDoubleTime)?.SpeedChange.IsDefault == true); - } - - [Test] - public void TestAnimationFlushOnClose() - { - changeRuleset(0); - - AddStep("Select all fun mods", () => - { - modSelect.ModSectionsContainer - .Single(c => c.ModType == ModType.DifficultyIncrease) - .SelectAll(); - }); - - AddUntilStep("many mods selected", () => modDisplay.Current.Value.Count >= 5); - - AddStep("trigger deselect and close overlay", () => - { - modSelect.ModSectionsContainer - .Single(c => c.ModType == ModType.DifficultyIncrease) - .DeselectAll(); - - modSelect.Hide(); - }); - - AddAssert("all mods deselected", () => modDisplay.Current.Value.Count == 0); - } - - [Test] - public void TestOsuMods() - { - changeRuleset(0); - - var osu = new OsuRuleset(); - - var easierMods = osu.GetModsFor(ModType.DifficultyReduction); - var harderMods = osu.GetModsFor(ModType.DifficultyIncrease); - - var noFailMod = osu.GetModsFor(ModType.DifficultyReduction).FirstOrDefault(m => m is OsuModNoFail); - - var doubleTimeMod = harderMods.OfType().FirstOrDefault(m => m.Mods.Any(a => a is OsuModDoubleTime)); - - var easy = easierMods.FirstOrDefault(m => m is OsuModEasy); - var hardRock = harderMods.FirstOrDefault(m => m is OsuModHardRock); - - testSingleMod(noFailMod); - testMultiMod(doubleTimeMod); - testIncompatibleMods(easy, hardRock); - testDeselectAll(easierMods.Where(m => !(m is MultiMod))); - } - - [Test] - public void TestManiaMods() - { - changeRuleset(3); - - var mania = new ManiaRuleset(); - - testModsWithSameBaseType( - mania.CreateMod(), - mania.CreateMod()); - } - - [Test] - public void TestRulesetChanges() - { - changeRuleset(0); - - var noFailMod = new OsuRuleset().GetModsFor(ModType.DifficultyReduction).FirstOrDefault(m => m is OsuModNoFail); - - AddStep("set mods externally", () => { SelectedMods.Value = new[] { noFailMod }; }); - - changeRuleset(0); - - AddAssert("ensure mods still selected", () => modDisplay.Current.Value.SingleOrDefault(m => m is OsuModNoFail) != null); - - changeRuleset(3); - - AddAssert("ensure mods not selected", () => modDisplay.Current.Value.Count == 0); - - changeRuleset(0); - - AddAssert("ensure mods not selected", () => modDisplay.Current.Value.Count == 0); - } - - [Test] - public void TestExternallySetCustomizedMod() - { - changeRuleset(0); - - AddStep("set customized mod externally", () => SelectedMods.Value = new[] { new OsuModDoubleTime { SpeedChange = { Value = 1.01 } } }); - - AddAssert("ensure button is selected and customized accordingly", () => - { - var button = modSelect.GetModButton(SelectedMods.Value.Single()); - return ((OsuModDoubleTime)button.SelectedMod).SpeedChange.Value == 1.01; - }); - } - - [Test] - public void TestSettingsAreRetainedOnReload() - { - changeRuleset(0); - - AddStep("set customized mod externally", () => SelectedMods.Value = new[] { new OsuModDoubleTime { SpeedChange = { Value = 1.01 } } }); - - AddAssert("setting remains", () => (SelectedMods.Value.SingleOrDefault() as OsuModDoubleTime)?.SpeedChange.Value == 1.01); - - AddStep("create overlay", () => createDisplay(() => new TestNonStackedModSelectOverlay())); - - AddAssert("setting remains", () => (SelectedMods.Value.SingleOrDefault() as OsuModDoubleTime)?.SpeedChange.Value == 1.01); - } - - [Test] - public void TestExternallySetModIsReplacedByOverlayInstance() - { - Mod external = new OsuModDoubleTime(); - Mod overlayButtonMod = null; - - changeRuleset(0); - - AddStep("set mod externally", () => { SelectedMods.Value = new[] { external }; }); - - AddAssert("ensure button is selected", () => - { - var button = modSelect.GetModButton(SelectedMods.Value.Single()); - overlayButtonMod = button.SelectedMod; - return overlayButtonMod.GetType() == external.GetType(); - }); - - // Right now, when an external change occurs, the ModSelectOverlay will replace the global instance with its own - AddAssert("mod instance doesn't match", () => external != overlayButtonMod); - - AddAssert("one mod present in global selected", () => SelectedMods.Value.Count == 1); - AddAssert("globally selected matches button's mod instance", () => SelectedMods.Value.Contains(overlayButtonMod)); - AddAssert("globally selected doesn't contain original external change", () => !SelectedMods.Value.Contains(external)); - } - - [Test] - public void TestNonStacked() - { - changeRuleset(0); - - AddStep("create overlay", () => createDisplay(() => new TestNonStackedModSelectOverlay())); - - AddStep("show", () => modSelect.Show()); - - AddAssert("ensure all buttons are spread out", () => modSelect.ChildrenOfType().All(m => m.Mods.Length <= 1)); - } - - [Test] - public void TestChangeIsValidChangesButtonVisibility() - { - changeRuleset(0); - - AddAssert("double time visible", () => modSelect.ChildrenOfType().Any(b => b.Mods.Any(m => m is OsuModDoubleTime))); - - AddStep("make double time invalid", () => modSelect.IsValidMod = m => !(m is OsuModDoubleTime)); - AddUntilStep("double time not visible", () => modSelect.ChildrenOfType().All(b => !b.Mods.Any(m => m is OsuModDoubleTime))); - AddAssert("nightcore still visible", () => modSelect.ChildrenOfType().Any(b => b.Mods.Any(m => m is OsuModNightcore))); - - AddStep("make double time valid again", () => modSelect.IsValidMod = m => true); - AddUntilStep("double time visible", () => modSelect.ChildrenOfType().Any(b => b.Mods.Any(m => m is OsuModDoubleTime))); - AddAssert("nightcore still visible", () => modSelect.ChildrenOfType().Any(b => b.Mods.Any(m => m is OsuModNightcore))); - } - - [Test] - public void TestChangeIsValidPreservesSelection() - { - changeRuleset(0); - - AddStep("select DT + HD", () => SelectedMods.Value = new Mod[] { new OsuModDoubleTime(), new OsuModHidden() }); - AddAssert("DT + HD selected", () => modSelect.ChildrenOfType().Count(b => b.Selected) == 2); - - AddStep("make NF invalid", () => modSelect.IsValidMod = m => !(m is ModNoFail)); - AddAssert("DT + HD still selected", () => modSelect.ChildrenOfType().Count(b => b.Selected) == 2); - } - - [Test] - public void TestUnimplementedModIsUnselectable() - { - var testRuleset = new TestUnimplementedModOsuRuleset(); - changeTestRuleset(testRuleset.RulesetInfo); - - var conversionMods = testRuleset.GetModsFor(ModType.Conversion); - - var unimplementedMod = conversionMods.FirstOrDefault(m => m is TestUnimplementedMod); - - testUnimplementedMod(unimplementedMod); - } - - private void testSingleMod(Mod mod) - { - selectNext(mod); - checkSelected(mod); - - selectPrevious(mod); - checkNotSelected(mod); - - selectNext(mod); - selectNext(mod); - checkNotSelected(mod); - - selectPrevious(mod); - selectPrevious(mod); - checkNotSelected(mod); - } - - private void testMultiMod(MultiMod multiMod) - { - foreach (var mod in multiMod.Mods) - { - selectNext(mod); - checkSelected(mod); - } - - for (int index = multiMod.Mods.Length - 1; index >= 0; index--) - selectPrevious(multiMod.Mods[index]); - - foreach (var mod in multiMod.Mods) - checkNotSelected(mod); - } - - private void testUnimplementedMod(Mod mod) - { - selectNext(mod); - checkNotSelected(mod); - } - - private void testIncompatibleMods(Mod modA, Mod modB) - { - selectNext(modA); - checkSelected(modA); - checkNotSelected(modB); - - selectNext(modB); - checkSelected(modB); - checkNotSelected(modA); - - selectPrevious(modB); - checkNotSelected(modA); - checkNotSelected(modB); - } - - private void testDeselectAll(IEnumerable mods) - { - foreach (var mod in mods) - selectNext(mod); - - AddAssert("check for any selection", () => modSelect.SelectedMods.Value.Any()); - AddStep("deselect all", () => modSelect.DeselectAllButton.Action.Invoke()); - AddAssert("check for no selection", () => !modSelect.SelectedMods.Value.Any()); - } - - private void testModsWithSameBaseType(Mod modA, Mod modB) - { - selectNext(modA); - checkSelected(modA); - selectNext(modB); - checkSelected(modB); - - // Backwards - selectPrevious(modA); - checkSelected(modA); - } - - private void selectNext(Mod mod) => AddStep($"left click {mod.Name}", () => modSelect.GetModButton(mod)?.SelectNext(1)); - - private void selectPrevious(Mod mod) => AddStep($"right click {mod.Name}", () => modSelect.GetModButton(mod)?.SelectNext(-1)); - - private void checkSelected(Mod mod) - { - AddAssert($"check {mod.Name} is selected", () => - { - var button = modSelect.GetModButton(mod); - return modSelect.SelectedMods.Value.SingleOrDefault(m => m.Name == mod.Name) != null && button.SelectedMod.GetType() == mod.GetType() && button.Selected; - }); - } - - private void changeRuleset(int? onlineId) - { - AddStep($"change ruleset to {(onlineId?.ToString() ?? "none")}", () => { Ruleset.Value = rulesets.AvailableRulesets.FirstOrDefault(r => r.OnlineID == onlineId); }); - waitForLoad(); - } - - private void changeTestRuleset(RulesetInfo rulesetInfo) - { - AddStep($"change ruleset to {rulesetInfo.Name}", () => { Ruleset.Value = rulesetInfo; }); - waitForLoad(); - } - - private void waitForLoad() => - AddUntilStep("wait for icons to load", () => modSelect.AllLoaded); - - private void checkNotSelected(Mod mod) - { - AddAssert($"check {mod.Name} is not selected", () => - { - var button = modSelect.GetModButton(mod); - return modSelect.SelectedMods.Value.All(m => m.GetType() != mod.GetType()) && button.SelectedMod?.GetType() != mod.GetType(); - }); - } - - private void createDisplay(Func createOverlayFunc) - { - Children = new Drawable[] - { - modSelect = createOverlayFunc().With(d => - { - d.Origin = Anchor.BottomCentre; - d.Anchor = Anchor.BottomCentre; - d.SelectedMods.BindTarget = SelectedMods; - }), - modDisplay = new ModDisplay - { - Anchor = Anchor.TopRight, - Origin = Anchor.TopRight, - Position = new Vector2(-5, 25), - Current = { BindTarget = modSelect.SelectedMods } - } - }; - } - - private class TestModSelectOverlay : UserModSelectOverlay - { - public new Bindable> SelectedMods => base.SelectedMods; - - public bool AllLoaded => ModSectionsContainer.Children.All(c => c.ModIconsLoaded); - - public new FillFlowContainer ModSectionsContainer => - base.ModSectionsContainer; - - public ModButton GetModButton(Mod mod) - { - var section = ModSectionsContainer.Children.Single(s => s.ModType == mod.Type); - return section.ButtonsContainer.OfType().Single(b => b.Mods.Any(m => m.GetType() == mod.GetType())); - } - - public new TriangleButton DeselectAllButton => base.DeselectAllButton; - - public new Color4 LowMultiplierColour => base.LowMultiplierColour; - public new Color4 HighMultiplierColour => base.HighMultiplierColour; - } - - private class TestNonStackedModSelectOverlay : TestModSelectOverlay - { - protected override bool Stacked => false; - } - - private class TestUnimplementedMod : Mod - { - public override string Name => "Unimplemented mod"; - public override string Acronym => "UM"; - public override string Description => "A mod that is not implemented."; - public override double ScoreMultiplier => 1; - public override ModType Type => ModType.Conversion; - } - - private class TestUnimplementedModOsuRuleset : OsuRuleset - { - public override string ShortName => "unimplemented"; - - public override IEnumerable GetModsFor(ModType type) - { - if (type == ModType.Conversion) return base.GetModsFor(type).Concat(new[] { new TestUnimplementedMod() }); - - return base.GetModsFor(type); - } - } - } -} diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModSettings.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModSettings.cs index 379735a7f5..1cf3b2651c 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModSettings.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModSettings.cs @@ -1,79 +1,15 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; -using System.Collections.Generic; -using System.Linq; using NUnit.Framework; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; using osu.Framework.Utils; -using osu.Framework.Testing; -using osu.Game.Graphics.UserInterface; -using osu.Game.Overlays.Mods; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu.Mods; -using osu.Game.Tests.Mods; -using osuTK.Input; namespace osu.Game.Tests.Visual.UserInterface { public class TestSceneModSettings : OsuManualInputManagerTestScene { - private TestModSelectOverlay modSelect; - - private readonly Mod testCustomisableMod = new TestCustomisableModRuleset.TestModCustomisable1(); - - private readonly Mod testCustomisableAutoOpenMod = new TestCustomisableModRuleset.TestModCustomisable2(); - - [SetUp] - public void SetUp() => Schedule(() => - { - SelectedMods.Value = Array.Empty(); - Ruleset.Value = TestCustomisableModRuleset.CreateTestRulesetInfo(); - }); - - [Test] - public void TestButtonShowsOnCustomisableMod() - { - createModSelect(); - openModSelect(); - - AddAssert("button disabled", () => !modSelect.CustomiseButton.Enabled.Value); - AddUntilStep("wait for button load", () => modSelect.ButtonsLoaded); - AddStep("select mod", () => modSelect.SelectMod(testCustomisableMod)); - AddAssert("button enabled", () => modSelect.CustomiseButton.Enabled.Value); - AddStep("open Customisation", () => modSelect.CustomiseButton.TriggerClick()); - AddStep("deselect mod", () => modSelect.SelectMod(testCustomisableMod)); - AddAssert("controls hidden", () => modSelect.ModSettingsContainer.State.Value == Visibility.Hidden); - } - - [Test] - public void TestButtonShowsOnModAlreadyAdded() - { - AddStep("set active mods", () => SelectedMods.Value = new List { testCustomisableMod }); - - createModSelect(); - - AddAssert("mods still active", () => SelectedMods.Value.Count == 1); - - openModSelect(); - AddAssert("button enabled", () => modSelect.CustomiseButton.Enabled.Value); - } - - [Test] - public void TestCustomisationMenuVisibility() - { - createModSelect(); - openModSelect(); - - AddAssert("Customisation closed", () => modSelect.ModSettingsContainer.State.Value == Visibility.Hidden); - AddStep("select mod", () => modSelect.SelectMod(testCustomisableAutoOpenMod)); - AddAssert("Customisation opened", () => modSelect.ModSettingsContainer.State.Value == Visibility.Visible); - AddStep("deselect mod", () => modSelect.SelectMod(testCustomisableAutoOpenMod)); - AddAssert("Customisation closed", () => modSelect.ModSettingsContainer.State.Value == Visibility.Hidden); - } - [Test] public void TestModSettingsUnboundWhenCopied() { @@ -109,60 +45,5 @@ namespace osu.Game.Tests.Visual.UserInterface AddAssert("original has new value", () => Precision.AlmostEquals(2.0, ((OsuModDoubleTime)original.Mods[0]).SpeedChange.Value)); AddAssert("copy has original value", () => Precision.AlmostEquals(1.5, ((OsuModDoubleTime)copy.Mods[0]).SpeedChange.Value)); } - - [Test] - public void TestCustomisationMenuNoClickthrough() - { - createModSelect(); - openModSelect(); - - AddStep("change mod settings menu width to full screen", () => modSelect.SetModSettingsWidth(1.0f)); - AddStep("select cm2", () => modSelect.SelectMod(testCustomisableAutoOpenMod)); - AddAssert("Customisation opened", () => modSelect.ModSettingsContainer.State.Value == Visibility.Visible); - AddStep("hover over mod behind settings menu", () => InputManager.MoveMouseTo(modSelect.GetModButton(testCustomisableMod))); - AddAssert("Mod is not considered hovered over", () => !modSelect.GetModButton(testCustomisableMod).IsHovered); - AddStep("left click mod", () => InputManager.Click(MouseButton.Left)); - AddAssert("only cm2 is active", () => SelectedMods.Value.Count == 1); - AddStep("right click mod", () => InputManager.Click(MouseButton.Right)); - AddAssert("only cm2 is active", () => SelectedMods.Value.Count == 1); - } - - private void createModSelect() - { - AddStep("create mod select", () => - { - Child = modSelect = new TestModSelectOverlay - { - Origin = Anchor.BottomCentre, - Anchor = Anchor.BottomCentre, - SelectedMods = { BindTarget = SelectedMods } - }; - }); - } - - private void openModSelect() - { - AddStep("open", () => modSelect.Show()); - AddUntilStep("wait for ready", () => modSelect.State.Value == Visibility.Visible && modSelect.ButtonsLoaded); - } - - private class TestModSelectOverlay : UserModSelectOverlay - { - public new VisibilityContainer ModSettingsContainer => base.ModSettingsContainer; - public new TriangleButton CustomiseButton => base.CustomiseButton; - - public bool ButtonsLoaded => ModSectionsContainer.Children.All(c => c.ModIconsLoaded); - - public ModButton GetModButton(Mod mod) - { - return ModSectionsContainer.ChildrenOfType().Single(b => b.Mods.Any(m => m.GetType() == mod.GetType())); - } - - public void SelectMod(Mod mod) => - GetModButton(mod).SelectNext(1); - - public void SetModSettingsWidth(float newWidth) => - ModSettingsContainer.Parent.Width = newWidth; - } } } diff --git a/osu.Game/Overlays/Mods/IncompatibilityDisplayingModButton.cs b/osu.Game/Overlays/Mods/IncompatibilityDisplayingModButton.cs deleted file mode 100644 index 6e2cb40596..0000000000 --- a/osu.Game/Overlays/Mods/IncompatibilityDisplayingModButton.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.Collections.Generic; -using System.Linq; -using osu.Framework.Allocation; -using osu.Framework.Bindables; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Cursor; -using osu.Game.Rulesets.Mods; -using osu.Game.Utils; -using osuTK; - -namespace osu.Game.Overlays.Mods -{ - public class IncompatibilityDisplayingModButton : ModButton - { - private readonly CompositeDrawable incompatibleIcon; - - [Resolved] - private Bindable> selectedMods { get; set; } - - public IncompatibilityDisplayingModButton(Mod mod) - : base(mod) - { - ButtonContent.Add(incompatibleIcon = new IncompatibleIcon - { - Anchor = Anchor.BottomRight, - Origin = Anchor.Centre, - Position = new Vector2(-13), - }); - } - - protected override void LoadComplete() - { - base.LoadComplete(); - - selectedMods.BindValueChanged(_ => Scheduler.AddOnce(updateCompatibility), true); - } - - protected override void DisplayMod(Mod mod) - { - base.DisplayMod(mod); - - Scheduler.AddOnce(updateCompatibility); - } - - private void updateCompatibility() - { - var m = SelectedMod ?? Mods.First(); - - bool isIncompatible = false; - - if (selectedMods.Value.Count > 0 && !selectedMods.Value.Contains(m)) - isIncompatible = !ModUtils.CheckCompatibleSet(selectedMods.Value.Append(m)); - - if (isIncompatible) - incompatibleIcon.Show(); - else - incompatibleIcon.Hide(); - } - - public override ITooltip GetCustomTooltip() => new IncompatibilityDisplayingTooltip(); - } -} diff --git a/osu.Game/Overlays/Mods/UserModSelectOverlay.cs b/osu.Game/Overlays/Mods/UserModSelectOverlay.cs deleted file mode 100644 index 161f89c2eb..0000000000 --- a/osu.Game/Overlays/Mods/UserModSelectOverlay.cs +++ /dev/null @@ -1,30 +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 osu.Game.Rulesets.Mods; - -namespace osu.Game.Overlays.Mods -{ - public class UserModSelectOverlay : ModSelectOverlay - { - protected override void OnModSelected(Mod mod) - { - base.OnModSelected(mod); - - foreach (var section in ModSectionsContainer.Children) - section.DeselectTypes(mod.IncompatibleMods, true, mod); - } - - protected override ModSection CreateModSection(ModType type) => new UserModSection(type); - - private class UserModSection : ModSection - { - public UserModSection(ModType type) - : base(type) - { - } - - protected override ModButton CreateModButton(Mod mod) => new IncompatibilityDisplayingModButton(mod); - } - } -} From 128468e13d87aa8f2ed54ddb4264732b96c2b1ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 10 May 2022 21:52:30 +0200 Subject: [PATCH 090/147] Remove old base mod select overlay --- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 532 --------------------- 1 file changed, 532 deletions(-) delete mode 100644 osu.Game/Overlays/Mods/ModSelectOverlay.cs diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs deleted file mode 100644 index cf57322594..0000000000 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ /dev/null @@ -1,532 +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 JetBrains.Annotations; -using osu.Framework.Allocation; -using osu.Framework.Audio; -using osu.Framework.Audio.Sample; -using osu.Framework.Bindables; -using osu.Framework.Extensions.Color4Extensions; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; -using osu.Framework.Input.Events; -using osu.Game.Graphics; -using osu.Game.Graphics.Backgrounds; -using osu.Game.Graphics.Containers; -using osu.Game.Graphics.Sprites; -using osu.Game.Graphics.UserInterface; -using osu.Game.Input.Bindings; -using osu.Game.Resources.Localisation.Web; -using osu.Game.Rulesets.Mods; -using osu.Game.Screens; -using osu.Game.Utils; -using osuTK; -using osuTK.Graphics; -using osuTK.Input; - -namespace osu.Game.Overlays.Mods -{ - public abstract class ModSelectOverlay : WaveOverlayContainer - { - public const float HEIGHT = 510; - - protected readonly TriangleButton DeselectAllButton; - protected readonly TriangleButton CustomiseButton; - protected readonly TriangleButton CloseButton; - - protected readonly FillFlowContainer FooterContainer; - - protected override bool BlockNonPositionalInput => false; - - protected override bool DimMainContent => false; - - /// - /// Whether s underneath the same instance should appear as stacked buttons. - /// - protected virtual bool Stacked => true; - - /// - /// Whether configurable s can be configured by the local user. - /// - protected virtual bool AllowConfiguration => true; - - [NotNull] - private Func isValidMod = m => true; - - /// - /// A function that checks whether a given mod is selectable. - /// - [NotNull] - public Func IsValidMod - { - get => isValidMod; - set - { - isValidMod = value ?? throw new ArgumentNullException(nameof(value)); - updateAvailableMods(); - } - } - - protected readonly FillFlowContainer ModSectionsContainer; - - protected readonly ModSettingsContainer ModSettingsContainer; - - [Cached] - public readonly Bindable> SelectedMods = new Bindable>(Array.Empty()); - - private Bindable>> availableMods; - - protected Color4 LowMultiplierColour; - protected Color4 HighMultiplierColour; - - private const float content_width = 0.8f; - private const float footer_button_spacing = 20; - - private Sample sampleOn, sampleOff; - - protected ModSelectOverlay() - { - Waves.FirstWaveColour = Color4Extensions.FromHex(@"19b0e2"); - Waves.SecondWaveColour = Color4Extensions.FromHex(@"2280a2"); - Waves.ThirdWaveColour = Color4Extensions.FromHex(@"005774"); - Waves.FourthWaveColour = Color4Extensions.FromHex(@"003a4e"); - - RelativeSizeAxes = Axes.X; - Height = HEIGHT; - - Padding = new MarginPadding { Horizontal = -OsuScreen.HORIZONTAL_OVERFLOW_PADDING }; - - Children = new Drawable[] - { - new Container - { - RelativeSizeAxes = Axes.Both, - Masking = true, - Children = new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = new Color4(36, 50, 68, 255) - }, - new Triangles - { - TriangleScale = 5, - RelativeSizeAxes = Axes.Both, - ColourLight = new Color4(53, 66, 82, 255), - ColourDark = new Color4(41, 54, 70, 255), - }, - }, - }, - new GridContainer - { - RelativeSizeAxes = Axes.Both, - Anchor = Anchor.BottomCentre, - Origin = Anchor.BottomCentre, - RowDimensions = new[] - { - new Dimension(GridSizeMode.Absolute, 90), - new Dimension(), - new Dimension(GridSizeMode.AutoSize), - }, - Content = new[] - { - new Drawable[] - { - new Container - { - RelativeSizeAxes = Axes.Both, - Origin = Anchor.TopCentre, - Anchor = Anchor.TopCentre, - Children = new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = OsuColour.Gray(10).Opacity(100), - }, - new FillFlowContainer - { - Origin = Anchor.Centre, - Anchor = Anchor.Centre, - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Direction = FillDirection.Vertical, - Width = content_width, - Padding = new MarginPadding { Horizontal = OsuScreen.HORIZONTAL_OVERFLOW_PADDING }, - Children = new Drawable[] - { - new OsuSpriteText - { - Text = @"Gameplay Mods", - Font = OsuFont.GetFont(size: 22, weight: FontWeight.Bold), - Shadow = true, - Margin = new MarginPadding - { - Bottom = 4, - }, - }, - new OsuTextFlowContainer(text => - { - text.Font = text.Font.With(size: 18); - text.Shadow = true; - }) - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Text = "Mods provide different ways to enjoy gameplay. Some have an effect on the score you can achieve during ranked play.\nOthers are just for fun.", - }, - }, - }, - }, - }, - }, - new Drawable[] - { - new Container - { - RelativeSizeAxes = Axes.Both, - Children = new Drawable[] - { - // Body - new OsuScrollContainer - { - ScrollbarVisible = false, - Origin = Anchor.TopCentre, - Anchor = Anchor.TopCentre, - RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding - { - Vertical = 10, - Horizontal = OsuScreen.HORIZONTAL_OVERFLOW_PADDING - }, - Children = new Drawable[] - { - ModSectionsContainer = new FillFlowContainer - { - Origin = Anchor.TopCentre, - Anchor = Anchor.TopCentre, - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Spacing = new Vector2(0f, 10f), - Width = content_width, - LayoutDuration = 200, - LayoutEasing = Easing.OutQuint, - Children = new[] - { - CreateModSection(ModType.DifficultyReduction).With(s => - { - s.ToggleKeys = new[] { Key.Q, Key.W, Key.E, Key.R, Key.T, Key.Y, Key.U, Key.I, Key.O, Key.P }; - s.Action = modButtonPressed; - }), - CreateModSection(ModType.DifficultyIncrease).With(s => - { - s.ToggleKeys = new[] { Key.A, Key.S, Key.D, Key.F, Key.G, Key.H, Key.J, Key.K, Key.L }; - s.Action = modButtonPressed; - }), - CreateModSection(ModType.Automation).With(s => - { - s.ToggleKeys = new[] { Key.Z, Key.X, Key.C, Key.V, Key.B, Key.N, Key.M }; - s.Action = modButtonPressed; - }), - CreateModSection(ModType.Conversion).With(s => - { - s.Action = modButtonPressed; - }), - CreateModSection(ModType.Fun).With(s => - { - s.Action = modButtonPressed; - }), - } - }, - } - }, - new Container - { - RelativeSizeAxes = Axes.Both, - Anchor = Anchor.BottomRight, - Origin = Anchor.BottomRight, - Padding = new MarginPadding(30), - Width = 0.3f, - Children = new Drawable[] - { - ModSettingsContainer = new ModSettingsContainer - { - Alpha = 0, - SelectedMods = { BindTarget = SelectedMods }, - }, - } - }, - } - }, - }, - new Drawable[] - { - new Container - { - Name = "Footer content", - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Origin = Anchor.TopCentre, - Anchor = Anchor.TopCentre, - Children = new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = new Color4(172, 20, 116, 255), - Alpha = 0.5f, - }, - FooterContainer = new FillFlowContainer - { - Origin = Anchor.BottomCentre, - Anchor = Anchor.BottomCentre, - AutoSizeAxes = Axes.Y, - RelativeSizeAxes = Axes.X, - RelativePositionAxes = Axes.X, - Width = content_width, - Spacing = new Vector2(footer_button_spacing, footer_button_spacing / 2), - Padding = new MarginPadding - { - Vertical = 15, - Horizontal = OsuScreen.HORIZONTAL_OVERFLOW_PADDING - }, - Children = new[] - { - DeselectAllButton = new TriangleButton - { - Width = 180, - Text = "Deselect All", - Action = deselectAll, - Origin = Anchor.CentreLeft, - Anchor = Anchor.CentreLeft, - }, - CustomiseButton = new TriangleButton - { - Width = 180, - Text = "Customisation", - Action = () => ModSettingsContainer.ToggleVisibility(), - Enabled = { Value = false }, - Alpha = AllowConfiguration ? 1 : 0, - Origin = Anchor.CentreLeft, - Anchor = Anchor.CentreLeft, - }, - CloseButton = new TriangleButton - { - Width = 180, - Text = CommonStrings.ButtonsClose, - Action = Hide, - Origin = Anchor.CentreLeft, - Anchor = Anchor.CentreLeft, - }, - } - } - }, - } - }, - }, - }, - }; - - ((IBindable)CustomiseButton.Enabled).BindTo(ModSettingsContainer.HasSettingsForSelection); - } - - [BackgroundDependencyLoader(true)] - private void load(AudioManager audio, OsuGameBase osu) - { - availableMods = osu.AvailableMods.GetBoundCopy(); - - sampleOn = audio.Samples.Get(@"UI/check-on"); - sampleOff = audio.Samples.Get(@"UI/check-off"); - } - - private void deselectAll() - { - foreach (var section in ModSectionsContainer.Children) - section.DeselectAll(); - - refreshSelectedMods(); - } - - protected override void LoadComplete() - { - base.LoadComplete(); - - availableMods.BindValueChanged(_ => updateAvailableMods(), true); - - // intentionally bound after the above line to avoid a potential update feedback cycle. - // i haven't actually observed this happening but as updateAvailableMods() changes the selection it is plausible. - SelectedMods.BindValueChanged(_ => updateSelectedButtons()); - } - - protected override void PopOut() - { - base.PopOut(); - - foreach (var section in ModSectionsContainer) - { - section.FlushPendingSelections(); - } - - FooterContainer.MoveToX(content_width, WaveContainer.DISAPPEAR_DURATION, Easing.InSine); - FooterContainer.FadeOut(WaveContainer.DISAPPEAR_DURATION, Easing.InSine); - - foreach (var section in ModSectionsContainer.Children) - { - section.ButtonsContainer.TransformSpacingTo(new Vector2(100f, 0f), WaveContainer.DISAPPEAR_DURATION, Easing.InSine); - section.ButtonsContainer.MoveToX(100f, WaveContainer.DISAPPEAR_DURATION, Easing.InSine); - section.ButtonsContainer.FadeOut(WaveContainer.DISAPPEAR_DURATION, Easing.InSine); - } - } - - protected override void PopIn() - { - base.PopIn(); - - FooterContainer.MoveToX(0, WaveContainer.APPEAR_DURATION, Easing.OutQuint); - FooterContainer.FadeIn(WaveContainer.APPEAR_DURATION, Easing.OutQuint); - - foreach (var section in ModSectionsContainer.Children) - { - section.ButtonsContainer.TransformSpacingTo(new Vector2(50f, 0f), WaveContainer.APPEAR_DURATION, Easing.OutQuint); - section.ButtonsContainer.MoveToX(0, WaveContainer.APPEAR_DURATION, Easing.OutQuint); - section.ButtonsContainer.FadeIn(WaveContainer.APPEAR_DURATION, Easing.OutQuint); - } - } - - protected override bool OnKeyDown(KeyDownEvent e) - { - // don't absorb control as ToolbarRulesetSelector uses control + number to navigate - if (e.ControlPressed) return false; - - switch (e.Key) - { - case Key.Number1: - DeselectAllButton.TriggerClick(); - return true; - - case Key.Number2: - CloseButton.TriggerClick(); - return true; - } - - return base.OnKeyDown(e); - } - - public override bool OnPressed(KeyBindingPressEvent e) => false; // handled by back button - - private void updateAvailableMods() - { - if (availableMods?.Value == null) - return; - - foreach (var section in ModSectionsContainer.Children) - { - IEnumerable modEnumeration = availableMods.Value[section.ModType]; - - if (!Stacked) - modEnumeration = ModUtils.FlattenMods(modEnumeration); - - section.Mods = modEnumeration.Select(getValidModOrNull).Where(m => m != null).Select(m => m.DeepClone()); - } - - updateSelectedButtons(); - OnAvailableModsChanged(); - } - - /// - /// Returns a valid form of a given if possible, or null otherwise. - /// - /// - /// This is a recursive process during which any invalid mods are culled while preserving structures where possible. - /// - /// The to check. - /// A valid form of if exists, or null otherwise. - [CanBeNull] - private Mod getValidModOrNull([NotNull] Mod mod) - { - if (!(mod is MultiMod multi)) - return IsValidMod(mod) ? mod : null; - - var validSubset = multi.Mods.Select(getValidModOrNull).Where(m => m != null).ToArray(); - - if (validSubset.Length == 0) - return null; - - return validSubset.Length == 1 ? validSubset[0] : new MultiMod(validSubset); - } - - private void updateSelectedButtons() - { - // Enumeration below may update the bindable list. - var selectedMods = SelectedMods.Value.ToList(); - - foreach (var section in ModSectionsContainer.Children) - section.UpdateSelectedButtons(selectedMods); - } - - private void modButtonPressed(Mod selectedMod) - { - if (selectedMod != null) - { - if (State.Value == Visibility.Visible) - Scheduler.AddOnce(playSelectedSound); - - OnModSelected(selectedMod); - - if (selectedMod.RequiresConfiguration && AllowConfiguration) - ModSettingsContainer.Show(); - } - else - { - if (State.Value == Visibility.Visible) - Scheduler.AddOnce(playDeselectedSound); - } - - refreshSelectedMods(); - } - - private void playSelectedSound() => sampleOn?.Play(); - private void playDeselectedSound() => sampleOff?.Play(); - - /// - /// Invoked after has changed. - /// - protected virtual void OnAvailableModsChanged() - { - } - - /// - /// Invoked when a new has been selected. - /// - /// The that has been selected. - protected virtual void OnModSelected(Mod mod) - { - } - - private void refreshSelectedMods() => SelectedMods.Value = ModSectionsContainer.Children.SelectMany(s => s.SelectedMods).ToArray(); - - /// - /// Creates a that groups s with the same . - /// - /// The of s in the section. - /// The . - protected virtual ModSection CreateModSection(ModType type) => new ModSection(type); - - #region Disposal - - protected override void Dispose(bool isDisposing) - { - base.Dispose(isDisposing); - - availableMods?.UnbindAll(); - SelectedMods?.UnbindAll(); - } - - #endregion - } -} From dfd97701d7d17ca559f0d681d8a669cbe2cafb19 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 10 May 2022 21:53:42 +0200 Subject: [PATCH 091/147] Remove old mod settings container & related controls --- osu.Game/Overlays/Mods/ModControlSection.cs | 54 --------- .../Overlays/Mods/ModSettingsContainer.cs | 111 ------------------ 2 files changed, 165 deletions(-) delete mode 100644 osu.Game/Overlays/Mods/ModControlSection.cs delete mode 100644 osu.Game/Overlays/Mods/ModSettingsContainer.cs diff --git a/osu.Game/Overlays/Mods/ModControlSection.cs b/osu.Game/Overlays/Mods/ModControlSection.cs deleted file mode 100644 index 10b3bc7c2b..0000000000 --- a/osu.Game/Overlays/Mods/ModControlSection.cs +++ /dev/null @@ -1,54 +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.Collections.Generic; -using osu.Framework.Allocation; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Game.Graphics; -using osu.Game.Graphics.Sprites; -using osu.Game.Rulesets.Mods; -using osuTK; - -namespace osu.Game.Overlays.Mods -{ - public class ModControlSection : CompositeDrawable - { - protected FillFlowContainer FlowContent; - - public readonly Mod Mod; - - public ModControlSection(Mod mod, IEnumerable modControls) - { - Mod = mod; - - RelativeSizeAxes = Axes.X; - AutoSizeAxes = Axes.Y; - - FlowContent = new FillFlowContainer - { - Margin = new MarginPadding { Top = 30 }, - Spacing = new Vector2(0, 5), - Direction = FillDirection.Vertical, - AutoSizeAxes = Axes.Y, - RelativeSizeAxes = Axes.X, - ChildrenEnumerable = modControls - }; - } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - AddRangeInternal(new Drawable[] - { - new OsuSpriteText - { - Text = Mod.Name, - Font = OsuFont.GetFont(weight: FontWeight.Bold), - Colour = colours.Yellow, - }, - FlowContent - }); - } - } -} diff --git a/osu.Game/Overlays/Mods/ModSettingsContainer.cs b/osu.Game/Overlays/Mods/ModSettingsContainer.cs deleted file mode 100644 index 64d65cab3b..0000000000 --- a/osu.Game/Overlays/Mods/ModSettingsContainer.cs +++ /dev/null @@ -1,111 +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.Bindables; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; -using osu.Framework.Input.Events; -using osu.Game.Configuration; -using osu.Game.Graphics.Containers; -using osu.Game.Rulesets.Mods; -using osuTK; -using osuTK.Graphics; - -namespace osu.Game.Overlays.Mods -{ - public class ModSettingsContainer : VisibilityContainer - { - public readonly IBindable> SelectedMods = new Bindable>(Array.Empty()); - - public IBindable HasSettingsForSelection => hasSettingsForSelection; - - private readonly Bindable hasSettingsForSelection = new Bindable(); - - private readonly FillFlowContainer modSettingsContent; - - private readonly Container content; - - private const double transition_duration = 400; - - public ModSettingsContainer() - { - RelativeSizeAxes = Axes.Both; - - Child = content = new Container - { - Masking = true, - CornerRadius = 10, - RelativeSizeAxes = Axes.Both, - RelativePositionAxes = Axes.Both, - X = 1, - Children = new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = new Color4(0, 0, 0, 192) - }, - new OsuScrollContainer - { - RelativeSizeAxes = Axes.Both, - ScrollbarVisible = false, - Child = modSettingsContent = new FillFlowContainer - { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Spacing = new Vector2(0f, 10f), - Padding = new MarginPadding(20), - } - } - } - }; - } - - protected override void LoadComplete() - { - base.LoadComplete(); - - SelectedMods.BindValueChanged(modsChanged, true); - } - - private void modsChanged(ValueChangedEvent> mods) - { - modSettingsContent.Clear(); - - foreach (var mod in mods.NewValue) - { - var settings = mod.CreateSettingsControls().ToList(); - if (settings.Count > 0) - modSettingsContent.Add(new ModControlSection(mod, settings)); - } - - bool hasSettings = modSettingsContent.Count > 0; - - if (!hasSettings) - Hide(); - - hasSettingsForSelection.Value = hasSettings; - } - - protected override bool OnMouseDown(MouseDownEvent e) => true; - protected override bool OnHover(HoverEvent e) => true; - - protected override void PopIn() - { - this.FadeIn(transition_duration, Easing.OutQuint); - content.MoveToX(0, transition_duration, Easing.OutQuint); - } - - protected override void PopOut() - { - this.FadeOut(transition_duration, Easing.OutQuint); - content.MoveToX(1, transition_duration, Easing.OutQuint); - } - } -} From 8b0ece1c09df40506f813a125bbd9c2f78546eb8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 10 May 2022 21:55:54 +0200 Subject: [PATCH 092/147] Remove old mod section --- osu.Game/Overlays/Mods/ModSection.cs | 261 --------------------------- 1 file changed, 261 deletions(-) delete mode 100644 osu.Game/Overlays/Mods/ModSection.cs diff --git a/osu.Game/Overlays/Mods/ModSection.cs b/osu.Game/Overlays/Mods/ModSection.cs deleted file mode 100644 index a70191a864..0000000000 --- a/osu.Game/Overlays/Mods/ModSection.cs +++ /dev/null @@ -1,261 +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 osuTK; -using osuTK.Input; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Game.Graphics.Sprites; -using osu.Game.Rulesets.Mods; -using System; -using System.Linq; -using System.Collections.Generic; -using System.Threading; -using Humanizer; -using osu.Framework.Input.Events; -using osu.Game.Graphics; - -namespace osu.Game.Overlays.Mods -{ - public class ModSection : CompositeDrawable - { - private readonly Drawable header; - - public FillFlowContainer ButtonsContainer { get; } - - protected IReadOnlyList Buttons { get; private set; } = Array.Empty(); - - public Action Action; - - public Key[] ToggleKeys; - - public readonly ModType ModType; - - public IEnumerable SelectedMods => Buttons.Select(b => b.SelectedMod).Where(m => m != null); - - private CancellationTokenSource modsLoadCts; - - protected bool SelectionAnimationRunning => pendingSelectionOperations.Count > 0; - - /// - /// True when all mod icons have completed loading. - /// - public bool ModIconsLoaded { get; private set; } = true; - - public IEnumerable Mods - { - set - { - var modContainers = value.Select(m => - { - if (m == null) - return new ModButtonEmpty(); - - return CreateModButton(m).With(b => - { - b.SelectionChanged = mod => - { - ModButtonStateChanged(mod); - Action?.Invoke(mod); - }; - }); - }).ToArray(); - - modsLoadCts?.Cancel(); - - if (modContainers.Length == 0) - { - ModIconsLoaded = true; - header.Hide(); - Hide(); - return; - } - - ModIconsLoaded = false; - - LoadComponentsAsync(modContainers, c => - { - ModIconsLoaded = true; - ButtonsContainer.ChildrenEnumerable = c; - }, (modsLoadCts = new CancellationTokenSource()).Token); - - Buttons = modContainers.OfType().ToArray(); - - header.FadeIn(200); - this.FadeIn(200); - } - } - - protected virtual void ModButtonStateChanged(Mod mod) - { - } - - protected override bool OnKeyDown(KeyDownEvent e) - { - if (e.ControlPressed) return false; - - if (ToggleKeys != null) - { - int index = Array.IndexOf(ToggleKeys, e.Key); - if (index > -1 && index < Buttons.Count) - Buttons[index].SelectNext(e.ShiftPressed ? -1 : 1); - } - - return base.OnKeyDown(e); - } - - private const double initial_multiple_selection_delay = 120; - - private double selectionDelay = initial_multiple_selection_delay; - private double lastSelection; - - private readonly Queue pendingSelectionOperations = new Queue(); - - protected override void Update() - { - base.Update(); - - if (selectionDelay == initial_multiple_selection_delay || Time.Current - lastSelection >= selectionDelay) - { - if (pendingSelectionOperations.TryDequeue(out var dequeuedAction)) - { - dequeuedAction(); - - // each time we play an animation, we decrease the time until the next animation (to ramp the visual and audible elements). - selectionDelay = Math.Max(30, selectionDelay * 0.8f); - lastSelection = Time.Current; - } - else - { - // reset the selection delay after all animations have been completed. - // this will cause the next action to be immediately performed. - selectionDelay = initial_multiple_selection_delay; - } - } - } - - /// - /// Selects all mods. - /// - public void SelectAll() - { - pendingSelectionOperations.Clear(); - - foreach (var button in Buttons.Where(b => !b.Selected)) - pendingSelectionOperations.Enqueue(() => button.SelectAt(0)); - } - - /// - /// Deselects all mods. - /// - public void DeselectAll() - { - pendingSelectionOperations.Clear(); - DeselectTypes(Buttons.Select(b => b.SelectedMod?.GetType()).Where(t => t != null)); - } - - /// - /// Deselect one or more mods in this section. - /// - /// The types of s which should be deselected. - /// Whether the deselection should happen immediately. Should only be used when required to ensure correct selection flow. - /// If this deselection is triggered by a user selection, this should contain the newly selected type. This type will never be deselected, even if it matches one provided in . - public void DeselectTypes(IEnumerable modTypes, bool immediate = false, Mod newSelection = null) - { - foreach (var button in Buttons) - { - if (button.SelectedMod == null) continue; - - if (button.SelectedMod == newSelection) - continue; - - foreach (var type in modTypes) - { - if (type.IsInstanceOfType(button.SelectedMod)) - { - if (immediate) - button.Deselect(); - else - pendingSelectionOperations.Enqueue(button.Deselect); - } - } - } - } - - /// - /// Updates all buttons with the given list of selected mods. - /// - /// The new list of selected mods to select. - public void UpdateSelectedButtons(IReadOnlyList newSelectedMods) - { - foreach (var button in Buttons) - updateButtonSelection(button, newSelectedMods); - } - - private void updateButtonSelection(ModButton button, IReadOnlyList newSelectedMods) - { - foreach (var mod in newSelectedMods) - { - int index = Array.FindIndex(button.Mods, m1 => mod.GetType() == m1.GetType()); - if (index < 0) - continue; - - var buttonMod = button.Mods[index]; - - // as this is likely coming from an external change, ensure the settings of the mod are in sync. - buttonMod.CopyFrom(mod); - - button.SelectAt(index, false); - return; - } - - button.Deselect(); - } - - public ModSection(ModType type) - { - ModType = type; - - AutoSizeAxes = Axes.Y; - RelativeSizeAxes = Axes.X; - - Origin = Anchor.TopCentre; - Anchor = Anchor.TopCentre; - - InternalChildren = new[] - { - header = CreateHeader(type.Humanize(LetterCasing.Title)), - ButtonsContainer = new FillFlowContainer - { - AutoSizeAxes = Axes.Y, - RelativeSizeAxes = Axes.X, - Origin = Anchor.BottomLeft, - Anchor = Anchor.BottomLeft, - Spacing = new Vector2(50f, 0f), - Margin = new MarginPadding - { - Top = 20, - }, - AlwaysPresent = true - }, - }; - } - - protected virtual Drawable CreateHeader(string text) => new OsuSpriteText - { - Font = OsuFont.GetFont(weight: FontWeight.Bold), - Text = text - }; - - protected virtual ModButton CreateModButton(Mod mod) => new ModButton(mod); - - /// - /// Run any delayed selections (due to animation) immediately to leave mods in a good (final) state. - /// - public void FlushPendingSelections() - { - while (pendingSelectionOperations.TryDequeue(out var dequeuedAction)) - dequeuedAction(); - } - } -} From 33634cba1eb7badf4821c04a7a21954b55d7f11d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 10 May 2022 21:58:22 +0200 Subject: [PATCH 093/147] Remove old mod buttons --- .../UserInterface/TestSceneModButton.cs | 64 ---- osu.Game/Overlays/Mods/IncompatibleIcon.cs | 64 ---- osu.Game/Overlays/Mods/ModButton.cs | 319 ------------------ osu.Game/Overlays/Mods/ModButtonEmpty.cs | 20 -- 4 files changed, 467 deletions(-) delete mode 100644 osu.Game.Tests/Visual/UserInterface/TestSceneModButton.cs delete mode 100644 osu.Game/Overlays/Mods/IncompatibleIcon.cs delete mode 100644 osu.Game/Overlays/Mods/ModButton.cs delete mode 100644 osu.Game/Overlays/Mods/ModButtonEmpty.cs diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModButton.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModButton.cs deleted file mode 100644 index fdc21d80ff..0000000000 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModButton.cs +++ /dev/null @@ -1,64 +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 osu.Framework.Graphics; -using osu.Framework.Graphics.Sprites; -using osu.Game.Overlays.Mods; -using osu.Game.Rulesets.Mods; - -namespace osu.Game.Tests.Visual.UserInterface -{ - public class TestSceneModButton : OsuTestScene - { - public TestSceneModButton() - { - Children = new Drawable[] - { - new ModButton(new MultiMod(new TestMod1(), new TestMod2(), new TestMod3(), new TestMod4())) - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre - } - }; - } - - private class TestMod1 : TestMod - { - public override string Name => "Test mod 1"; - - public override string Acronym => "M1"; - } - - private class TestMod2 : TestMod - { - public override string Name => "Test mod 2"; - - public override string Acronym => "M2"; - - public override IconUsage? Icon => FontAwesome.Solid.Exclamation; - } - - private class TestMod3 : TestMod - { - public override string Name => "Test mod 3"; - - public override string Acronym => "M3"; - - public override IconUsage? Icon => FontAwesome.Solid.ArrowRight; - } - - private class TestMod4 : TestMod - { - public override string Name => "Test mod 4"; - - public override string Acronym => "M4"; - } - - private abstract class TestMod : Mod, IApplicableMod - { - public override double ScoreMultiplier => 1.0; - - public override string Description => "This is a test mod."; - } - } -} diff --git a/osu.Game/Overlays/Mods/IncompatibleIcon.cs b/osu.Game/Overlays/Mods/IncompatibleIcon.cs deleted file mode 100644 index df134fe4a4..0000000000 --- a/osu.Game/Overlays/Mods/IncompatibleIcon.cs +++ /dev/null @@ -1,64 +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 osu.Framework.Allocation; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Cursor; -using osu.Framework.Graphics.Shapes; -using osu.Framework.Graphics.Sprites; -using osu.Framework.Localisation; -using osu.Game.Graphics; -using osuTK; -using osuTK.Graphics; - -namespace osu.Game.Overlays.Mods -{ - public class IncompatibleIcon : VisibilityContainer, IHasTooltip - { - private Circle circle; - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - Size = new Vector2(20); - - State.Value = Visibility.Hidden; - Alpha = 0; - - InternalChildren = new Drawable[] - { - circle = new Circle - { - RelativeSizeAxes = Axes.Both, - Colour = colours.Gray4, - }, - new SpriteIcon - { - RelativeSizeAxes = Axes.Both, - Origin = Anchor.Centre, - Anchor = Anchor.Centre, - Size = new Vector2(0.6f), - Icon = FontAwesome.Solid.Slash, - Colour = Color4.White, - Shadow = true, - } - }; - } - - protected override void PopIn() - { - this.FadeIn(200, Easing.OutQuint); - circle.FlashColour(Color4.Red, 500, Easing.OutQuint); - this.ScaleTo(1.8f).Then().ScaleTo(1, 500, Easing.OutQuint); - } - - protected override void PopOut() - { - this.FadeOut(200, Easing.OutQuint); - this.ScaleTo(0.8f, 200, Easing.In); - } - - public LocalisableString TooltipText => "Incompatible with current selected mods"; - } -} diff --git a/osu.Game/Overlays/Mods/ModButton.cs b/osu.Game/Overlays/Mods/ModButton.cs deleted file mode 100644 index 979e2c8da3..0000000000 --- a/osu.Game/Overlays/Mods/ModButton.cs +++ /dev/null @@ -1,319 +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 osuTK; -using osuTK.Graphics; -using osuTK.Input; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Sprites; -using osu.Game.Graphics.Sprites; -using osu.Game.Rulesets.Mods; -using osu.Game.Rulesets.UI; -using System; -using System.Linq; -using osu.Framework.Graphics.Cursor; -using osu.Framework.Input.Events; -using osu.Framework.Localisation; -using osu.Game.Graphics; -using osu.Game.Graphics.UserInterface; - -namespace osu.Game.Overlays.Mods -{ - /// - /// Represents a clickable button which can cycle through one of more mods. - /// - public class ModButton : ModButtonEmpty, IHasCustomTooltip - { - private ModIcon foregroundIcon; - private ModIcon backgroundIcon; - private readonly SpriteText text; - private readonly Container iconsContainer; - - /// - /// Fired when the selection changes. - /// - public Action SelectionChanged; - - public LocalisableString TooltipText => (SelectedMod?.Description ?? Mods.FirstOrDefault()?.Description) ?? string.Empty; - - private const Easing mod_switch_easing = Easing.InOutSine; - private const double mod_switch_duration = 120; - - // A selected index of -1 means not selected. - private int selectedIndex = -1; - - /// - /// Change the selected mod index of this button. - /// - /// The new index. - /// Whether any settings applied to the mod should be reset on selection. - /// Whether the selection changed. - private bool changeSelectedIndex(int newIndex, bool resetSettings = true) - { - if (newIndex == selectedIndex) return false; - - int direction = newIndex < selectedIndex ? -1 : 1; - - bool beforeSelected = Selected; - - Mod previousSelection = SelectedMod ?? Mods[0]; - - if (newIndex >= Mods.Length) - newIndex = -1; - else if (newIndex < -1) - newIndex = Mods.Length - 1; - - if (newIndex >= 0 && !Mods[newIndex].HasImplementation) - return false; - - selectedIndex = newIndex; - - Mod newSelection = SelectedMod ?? Mods[0]; - - if (resetSettings) - newSelection.ResetSettingsToDefaults(); - - Schedule(() => - { - if (beforeSelected != Selected) - { - iconsContainer.RotateTo(Selected ? 5f : 0f, 300, Easing.OutElastic); - iconsContainer.ScaleTo(Selected ? 1.1f : 1f, 300, Easing.OutElastic); - } - - if (previousSelection != newSelection) - { - const float rotate_angle = 16; - - foregroundIcon.RotateTo(rotate_angle * direction, mod_switch_duration, mod_switch_easing); - backgroundIcon.RotateTo(-rotate_angle * direction, mod_switch_duration, mod_switch_easing); - - backgroundIcon.Mod = newSelection; - - using (BeginDelayedSequence(mod_switch_duration)) - { - foregroundIcon - .RotateTo(-rotate_angle * direction) - .RotateTo(0f, mod_switch_duration, mod_switch_easing); - - backgroundIcon - .RotateTo(rotate_angle * direction) - .RotateTo(0f, mod_switch_duration, mod_switch_easing); - - Schedule(() => DisplayMod(newSelection)); - } - } - - foregroundIcon.Selected.Value = Selected; - }); - - SelectionChanged?.Invoke(SelectedMod); - - return true; - } - - public bool Selected => selectedIndex != -1; - - private Color4 selectedColour; - - public Color4 SelectedColour - { - get => selectedColour; - set - { - if (value == selectedColour) return; - - selectedColour = value; - if (Selected) foregroundIcon.Colour = value; - } - } - - private Mod mod; - - protected readonly Container ButtonContent; - - public Mod Mod - { - get => mod; - set - { - mod = value; - - if (mod == null) - { - Mods = Array.Empty(); - Alpha = 0; - } - else - { - Mods = (mod as MultiMod)?.Mods ?? new[] { mod }; - Alpha = 1; - } - - createIcons(); - - if (Mods.Length > 0) - { - DisplayMod(Mods[0]); - } - } - } - - public Mod[] Mods { get; private set; } - - public virtual Mod SelectedMod => Mods.ElementAtOrDefault(selectedIndex); - - protected override bool OnMouseDown(MouseDownEvent e) - { - ButtonContent.ScaleTo(0.9f, 800, Easing.Out); - return base.OnMouseDown(e); - } - - protected override void OnMouseUp(MouseUpEvent e) - { - ButtonContent.ScaleTo(1, 500, Easing.OutElastic); - - // only trigger the event if we are inside the area of the button - if (Contains(e.ScreenSpaceMousePosition)) - { - switch (e.Button) - { - case MouseButton.Right: - SelectNext(-1); - break; - } - } - } - - protected override bool OnClick(ClickEvent e) - { - SelectNext(1); - - return true; - } - - /// - /// Select the next available mod in a specified direction. - /// - /// 1 for forwards, -1 for backwards. - public void SelectNext(int direction) - { - int start = selectedIndex + direction; - // wrap around if we are at an extremity. - if (start >= Mods.Length) - start = -1; - else if (start < -1) - start = Mods.Length - 1; - - for (int i = start; i < Mods.Length && i >= 0; i += direction) - { - if (SelectAt(i)) - return; - } - - Deselect(); - } - - /// - /// Select the mod at the provided index. - /// - /// The index to select. - /// Whether any settings applied to the mod should be reset on selection. - /// Whether the selection changed. - public bool SelectAt(int index, bool resetSettings = true) - { - if (!Mods[index].HasImplementation) return false; - - changeSelectedIndex(index, resetSettings); - return true; - } - - public void Deselect() => changeSelectedIndex(-1); - - protected virtual void DisplayMod(Mod mod) - { - if (backgroundIcon != null) - backgroundIcon.Mod = foregroundIcon.Mod; - foregroundIcon.Mod = mod; - text.Text = mod.Name; - Colour = mod.HasImplementation ? Color4.White : Color4.Gray; - } - - private void createIcons() - { - iconsContainer.Clear(); - - if (Mods.Length > 1) - { - iconsContainer.AddRange(new[] - { - backgroundIcon = new ModIcon(Mods[1], false) - { - Origin = Anchor.BottomRight, - Anchor = Anchor.BottomRight, - Position = new Vector2(1.5f), - }, - foregroundIcon = new ModIcon(Mods[0], false) - { - Origin = Anchor.BottomRight, - Anchor = Anchor.BottomRight, - Position = new Vector2(-1.5f), - }, - }); - } - else - { - iconsContainer.Add(foregroundIcon = new ModIcon(Mod, false) - { - Origin = Anchor.Centre, - Anchor = Anchor.Centre, - }); - } - } - - public ModButton(Mod mod) - { - Children = new Drawable[] - { - new Container - { - Size = new Vector2(77f, 80f), - Origin = Anchor.TopCentre, - Anchor = Anchor.TopCentre, - Children = new Drawable[] - { - ButtonContent = new Container - { - Children = new Drawable[] - { - iconsContainer = new Container - { - RelativeSizeAxes = Axes.Both, - Origin = Anchor.Centre, - Anchor = Anchor.Centre, - }, - }, - RelativeSizeAxes = Axes.Both, - Origin = Anchor.Centre, - Anchor = Anchor.Centre, - } - } - }, - text = new OsuSpriteText - { - Y = 75, - Origin = Anchor.TopCentre, - Anchor = Anchor.TopCentre, - Font = OsuFont.GetFont(size: 18) - }, - new HoverSounds() - }; - Mod = mod; - } - - public virtual ITooltip GetCustomTooltip() => new ModButtonTooltip(); - - public Mod TooltipContent => SelectedMod ?? Mods.FirstOrDefault(); - } -} diff --git a/osu.Game/Overlays/Mods/ModButtonEmpty.cs b/osu.Game/Overlays/Mods/ModButtonEmpty.cs deleted file mode 100644 index 03afe5adba..0000000000 --- a/osu.Game/Overlays/Mods/ModButtonEmpty.cs +++ /dev/null @@ -1,20 +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 osuTK; -using osu.Framework.Graphics.Containers; - -namespace osu.Game.Overlays.Mods -{ - /// - /// A mod button used exclusively for providing an empty space the size of a mod button. - /// - public class ModButtonEmpty : Container - { - public ModButtonEmpty() - { - Size = new Vector2(100f); - AlwaysPresent = true; - } - } -} From c4c7556fb24407b7c97067729a6bd6c894a71eb5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 10 May 2022 22:07:24 +0200 Subject: [PATCH 094/147] Make remains of `TestSceneModSettings` non-visual As they're not really testing anything visual. --- osu.Game.Tests/Mods/ModSettingsTest.cs | 36 ++++++++++++++ .../UserInterface/TestSceneModSettings.cs | 49 ------------------- 2 files changed, 36 insertions(+), 49 deletions(-) create mode 100644 osu.Game.Tests/Mods/ModSettingsTest.cs delete mode 100644 osu.Game.Tests/Visual/UserInterface/TestSceneModSettings.cs diff --git a/osu.Game.Tests/Mods/ModSettingsTest.cs b/osu.Game.Tests/Mods/ModSettingsTest.cs new file mode 100644 index 0000000000..b9ea1f2567 --- /dev/null +++ b/osu.Game.Tests/Mods/ModSettingsTest.cs @@ -0,0 +1,36 @@ +// 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.Rulesets.Mods; +using osu.Game.Rulesets.Osu.Mods; + +namespace osu.Game.Tests.Mods +{ + public class ModSettingsTest + { + [Test] + public void TestModSettingsUnboundWhenCopied() + { + var original = new OsuModDoubleTime(); + var copy = (OsuModDoubleTime)original.DeepClone(); + + original.SpeedChange.Value = 2; + + Assert.That(original.SpeedChange.Value, Is.EqualTo(2.0)); + Assert.That(copy.SpeedChange.Value, Is.EqualTo(1.5)); + } + + [Test] + public void TestMultiModSettingsUnboundWhenCopied() + { + var original = new MultiMod(new OsuModDoubleTime()); + var copy = (MultiMod)original.DeepClone(); + + ((OsuModDoubleTime)original.Mods[0]).SpeedChange.Value = 2; + + Assert.That(((OsuModDoubleTime)original.Mods[0]).SpeedChange.Value, Is.EqualTo(2.0)); + Assert.That(((OsuModDoubleTime)copy.Mods[0]).SpeedChange.Value, Is.EqualTo(1.5)); + } + } +} diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModSettings.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModSettings.cs deleted file mode 100644 index 1cf3b2651c..0000000000 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModSettings.cs +++ /dev/null @@ -1,49 +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 NUnit.Framework; -using osu.Framework.Utils; -using osu.Game.Rulesets.Mods; -using osu.Game.Rulesets.Osu.Mods; - -namespace osu.Game.Tests.Visual.UserInterface -{ - public class TestSceneModSettings : OsuManualInputManagerTestScene - { - [Test] - public void TestModSettingsUnboundWhenCopied() - { - OsuModDoubleTime original = null; - OsuModDoubleTime copy = null; - - AddStep("create mods", () => - { - original = new OsuModDoubleTime(); - copy = (OsuModDoubleTime)original.DeepClone(); - }); - - AddStep("change property", () => original.SpeedChange.Value = 2); - - AddAssert("original has new value", () => Precision.AlmostEquals(2.0, original.SpeedChange.Value)); - AddAssert("copy has original value", () => Precision.AlmostEquals(1.5, copy.SpeedChange.Value)); - } - - [Test] - public void TestMultiModSettingsUnboundWhenCopied() - { - MultiMod original = null; - MultiMod copy = null; - - AddStep("create mods", () => - { - original = new MultiMod(new OsuModDoubleTime()); - copy = (MultiMod)original.DeepClone(); - }); - - AddStep("change property", () => ((OsuModDoubleTime)original.Mods[0]).SpeedChange.Value = 2); - - AddAssert("original has new value", () => Precision.AlmostEquals(2.0, ((OsuModDoubleTime)original.Mods[0]).SpeedChange.Value)); - AddAssert("copy has original value", () => Precision.AlmostEquals(1.5, ((OsuModDoubleTime)copy.Mods[0]).SpeedChange.Value)); - } - } -} From 76c63f1d0a0a2bda6eb56642c0f417ba8656297c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 10 May 2022 22:29:57 +0200 Subject: [PATCH 095/147] Rename `ModSelect{Screen -> Overlay}` in place of removed old design --- .../TestSceneFreeModSelectScreen.cs | 18 ++-- .../Multiplayer/TestSceneMultiplayer.cs | 2 +- .../TestSceneMultiplayerMatchSongSelect.cs | 2 +- .../TestSceneMultiplayerMatchSubScreen.cs | 2 +- .../Navigation/TestSceneScreenNavigation.cs | 2 +- .../SongSelect/TestScenePlaySongSelect.cs | 2 +- .../UserInterface/TestSceneModSelectScreen.cs | 96 +++++++++---------- osu.Game/Overlays/Mods/ModColumn.cs | 2 +- ...ModSelectScreen.cs => ModSelectOverlay.cs} | 4 +- ...electScreen.cs => UserModSelectOverlay.cs} | 4 +- ...electScreen.cs => FreeModSelectOverlay.cs} | 4 +- .../Screens/OnlinePlay/Match/RoomSubScreen.cs | 4 +- .../OnlinePlay/OnlinePlaySongSelect.cs | 6 +- osu.Game/Screens/Select/SongSelect.cs | 4 +- 14 files changed, 76 insertions(+), 76 deletions(-) rename osu.Game/Overlays/Mods/{ModSelectScreen.cs => ModSelectOverlay.cs} (99%) rename osu.Game/Overlays/Mods/{UserModSelectScreen.cs => UserModSelectOverlay.cs} (92%) rename osu.Game/Screens/OnlinePlay/{FreeModSelectScreen.cs => FreeModSelectOverlay.cs} (94%) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneFreeModSelectScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneFreeModSelectScreen.cs index 4eb14542ba..9a24a1c646 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneFreeModSelectScreen.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneFreeModSelectScreen.cs @@ -19,7 +19,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { public class TestSceneFreeModSelectScreen : MultiplayerTestScene { - private FreeModSelectScreen freeModSelectScreen; + private FreeModSelectOverlay freeModSelectOverlay; private readonly Bindable>> availableMods = new Bindable>>(); [BackgroundDependencyLoader] @@ -40,8 +40,8 @@ namespace osu.Game.Tests.Visual.Multiplayer AddToggleStep("toggle visibility", visible => { - if (freeModSelectScreen != null) - freeModSelectScreen.State.Value = visible ? Visibility.Visible : Visibility.Hidden; + if (freeModSelectOverlay != null) + freeModSelectOverlay.State.Value = visible ? Visibility.Visible : Visibility.Hidden; }); } @@ -50,7 +50,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { createFreeModSelect(); - AddStep("select difficulty adjust", () => freeModSelectScreen.SelectedMods.Value = new[] { new OsuModDifficultyAdjust() }); + AddStep("select difficulty adjust", () => freeModSelectOverlay.SelectedMods.Value = new[] { new OsuModDifficultyAdjust() }); AddWaitStep("wait some", 3); AddAssert("customisation area not expanded", () => this.ChildrenOfType().Single().Height == 0); } @@ -72,18 +72,18 @@ namespace osu.Game.Tests.Visual.Multiplayer InputManager.MoveMouseTo(this.ChildrenOfType().Last()); InputManager.Click(MouseButton.Left); }); - AddUntilStep("all mods deselected", () => !freeModSelectScreen.SelectedMods.Value.Any()); + AddUntilStep("all mods deselected", () => !freeModSelectOverlay.SelectedMods.Value.Any()); } private void createFreeModSelect() { - AddStep("create free mod select screen", () => Child = freeModSelectScreen = new FreeModSelectScreen + AddStep("create free mod select screen", () => Child = freeModSelectOverlay = new FreeModSelectOverlay { State = { Value = Visibility.Visible } }); AddUntilStep("all column content loaded", - () => freeModSelectScreen.ChildrenOfType().Any() - && freeModSelectScreen.ChildrenOfType().All(column => column.IsLoaded && column.ItemsLoaded)); + () => freeModSelectOverlay.ChildrenOfType().Any() + && freeModSelectOverlay.ChildrenOfType().All(column => column.IsLoaded && column.ItemsLoaded)); } private bool assertAllAvailableModsSelected() @@ -95,7 +95,7 @@ namespace osu.Game.Tests.Visual.Multiplayer foreach (var availableMod in allAvailableMods) { - if (freeModSelectScreen.SelectedMods.Value.All(selectedMod => selectedMod.GetType() != availableMod.GetType())) + if (freeModSelectOverlay.SelectedMods.Value.All(selectedMod => selectedMod.GetType() != availableMod.GetType())) return false; } diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs index 97cb08e3c1..8e45d99eae 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs @@ -627,7 +627,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("invoke on back button", () => multiplayerComponents.OnBackButton()); - AddAssert("mod overlay is hidden", () => this.ChildrenOfType().Single().State.Value == Visibility.Hidden); + AddAssert("mod overlay is hidden", () => this.ChildrenOfType().Single().State.Value == Visibility.Hidden); AddAssert("dialog overlay is hidden", () => DialogOverlay.State.Value == Visibility.Hidden); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs index 061fe5715b..eacd80925d 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs @@ -132,7 +132,7 @@ namespace osu.Game.Tests.Visual.Multiplayer private void assertHasFreeModButton(Type type, bool hasButton = true) { AddAssert($"{type.ReadableName()} {(hasButton ? "displayed" : "not displayed")} in freemod overlay", - () => this.ChildrenOfType() + () => this.ChildrenOfType() .Single() .ChildrenOfType() .Where(panel => !panel.Filtered.Value) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs index 6173580f0b..ca79fa9cb8 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs @@ -172,7 +172,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddUntilStep("mod select contents loaded", () => this.ChildrenOfType().Any() && this.ChildrenOfType().All(col => col.IsLoaded && col.ItemsLoaded)); AddUntilStep("mod select contains only double time mod", - () => this.ChildrenOfType() + () => this.ChildrenOfType() .SingleOrDefault()? .ChildrenOfType() .SingleOrDefault(panel => !panel.Filtered.Value)?.Mod is OsuModDoubleTime); diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs index 9674ef7ae1..a3e0caedb9 100644 --- a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs +++ b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs @@ -568,7 +568,7 @@ namespace osu.Game.Tests.Visual.Navigation public class TestPlaySongSelect : PlaySongSelect { - public ModSelectScreen ModSelectOverlay => ModSelect; + public ModSelectOverlay ModSelectOverlay => ModSelect; public BeatmapOptionsOverlay BeatmapOptionsOverlay => BeatmapOptions; diff --git a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs index 083e24be7b..aad7f6b301 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs @@ -1008,7 +1008,7 @@ namespace osu.Game.Tests.Visual.SongSelect public WorkingBeatmap CurrentBeatmap => Beatmap.Value; public IWorkingBeatmap CurrentBeatmapDetailsBeatmap => BeatmapDetails.Beatmap; public new BeatmapCarousel Carousel => base.Carousel; - public new ModSelectScreen ModSelect => base.ModSelect; + public new ModSelectOverlay ModSelect => base.ModSelect; public new void PresentScore(ScoreInfo score) => base.PresentScore(score); diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectScreen.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectScreen.cs index fa7758df59..2e2ce6edd7 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectScreen.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectScreen.cs @@ -29,7 +29,7 @@ namespace osu.Game.Tests.Visual.UserInterface [Resolved] private RulesetStore rulesetStore { get; set; } - private UserModSelectScreen modSelectScreen; + private UserModSelectOverlay modSelectOverlay; [SetUpSteps] public void SetUpSteps() @@ -41,7 +41,7 @@ namespace osu.Game.Tests.Visual.UserInterface private void createScreen() { - AddStep("create screen", () => Child = modSelectScreen = new UserModSelectScreen + AddStep("create screen", () => Child = modSelectOverlay = new UserModSelectOverlay { RelativeSizeAxes = Axes.Both, State = { Value = Visibility.Visible }, @@ -54,7 +54,7 @@ namespace osu.Game.Tests.Visual.UserInterface public void TestStateChange() { createScreen(); - AddStep("toggle state", () => modSelectScreen.ToggleVisibility()); + AddStep("toggle state", () => modSelectOverlay.ToggleVisibility()); } [Test] @@ -62,14 +62,14 @@ namespace osu.Game.Tests.Visual.UserInterface { AddStep("set mods", () => SelectedMods.Value = new Mod[] { new OsuModAlternate(), new OsuModDaycore() }); createScreen(); - AddUntilStep("two panels active", () => modSelectScreen.ChildrenOfType().Count(panel => panel.Active.Value) == 2); + AddUntilStep("two panels active", () => modSelectOverlay.ChildrenOfType().Count(panel => panel.Active.Value) == 2); AddAssert("mod multiplier correct", () => { double multiplier = SelectedMods.Value.Aggregate(1d, (m, mod) => m * mod.ScoreMultiplier); - return Precision.AlmostEquals(multiplier, modSelectScreen.ChildrenOfType().Single().Current.Value); + return Precision.AlmostEquals(multiplier, modSelectOverlay.ChildrenOfType().Single().Current.Value); }); assertCustomisationToggleState(disabled: false, active: false); - AddAssert("setting items created", () => modSelectScreen.ChildrenOfType().Any()); + AddAssert("setting items created", () => modSelectOverlay.ChildrenOfType().Any()); } [Test] @@ -77,14 +77,14 @@ namespace osu.Game.Tests.Visual.UserInterface { createScreen(); AddStep("set mods", () => SelectedMods.Value = new Mod[] { new OsuModAlternate(), new OsuModDaycore() }); - AddUntilStep("two panels active", () => modSelectScreen.ChildrenOfType().Count(panel => panel.Active.Value) == 2); + AddUntilStep("two panels active", () => modSelectOverlay.ChildrenOfType().Count(panel => panel.Active.Value) == 2); AddAssert("mod multiplier correct", () => { double multiplier = SelectedMods.Value.Aggregate(1d, (m, mod) => m * mod.ScoreMultiplier); - return Precision.AlmostEquals(multiplier, modSelectScreen.ChildrenOfType().Single().Current.Value); + return Precision.AlmostEquals(multiplier, modSelectOverlay.ChildrenOfType().Single().Current.Value); }); assertCustomisationToggleState(disabled: false, active: false); - AddAssert("setting items created", () => modSelectScreen.ChildrenOfType().Any()); + AddAssert("setting items created", () => modSelectOverlay.ChildrenOfType().Any()); } [Test] @@ -139,7 +139,7 @@ namespace osu.Game.Tests.Visual.UserInterface AddAssert("last column dimmed", () => !this.ChildrenOfType().Last().Active.Value); AddStep("request scroll to last column", () => { - var lastDimContainer = this.ChildrenOfType().Last(); + var lastDimContainer = this.ChildrenOfType().Last(); lastColumn = lastDimContainer.Column; lastDimContainer.RequestScroll?.Invoke(lastDimContainer); }); @@ -167,7 +167,7 @@ namespace osu.Game.Tests.Visual.UserInterface AddStep("dismiss mod customisation via toggle", () => { - InputManager.MoveMouseTo(modSelectScreen.ChildrenOfType().Single()); + InputManager.MoveMouseTo(modSelectOverlay.ChildrenOfType().Single()); InputManager.Click(MouseButton.Left); }); assertCustomisationToggleState(disabled: false, active: false); @@ -205,14 +205,14 @@ namespace osu.Game.Tests.Visual.UserInterface AddStep("move mouse to dimmed area", () => { InputManager.MoveMouseTo(new Vector2( - modSelectScreen.ScreenSpaceDrawQuad.TopLeft.X, - (modSelectScreen.ScreenSpaceDrawQuad.TopLeft.Y + modSelectScreen.ScreenSpaceDrawQuad.BottomLeft.Y) / 2)); + modSelectOverlay.ScreenSpaceDrawQuad.TopLeft.X, + (modSelectOverlay.ScreenSpaceDrawQuad.TopLeft.Y + modSelectOverlay.ScreenSpaceDrawQuad.BottomLeft.Y) / 2)); }); AddStep("click", () => InputManager.Click(MouseButton.Left)); assertCustomisationToggleState(disabled: false, active: false); - AddStep("move mouse to first mod panel", () => InputManager.MoveMouseTo(modSelectScreen.ChildrenOfType().First())); - AddAssert("first mod panel is hovered", () => modSelectScreen.ChildrenOfType().First().IsHovered); + AddStep("move mouse to first mod panel", () => InputManager.MoveMouseTo(modSelectOverlay.ChildrenOfType().First())); + AddAssert("first mod panel is hovered", () => modSelectOverlay.ChildrenOfType().First().IsHovered); } /// @@ -222,12 +222,12 @@ namespace osu.Game.Tests.Visual.UserInterface public void TestSettingsNotCrossPolluting() { Bindable> selectedMods2 = null; - ModSelectScreen modSelectScreen2 = null; + ModSelectOverlay modSelectScreen2 = null; createScreen(); AddStep("select diff adjust", () => SelectedMods.Value = new Mod[] { new OsuModDifficultyAdjust() }); - AddStep("set setting", () => modSelectScreen.ChildrenOfType>().First().Current.Value = 8); + AddStep("set setting", () => modSelectOverlay.ChildrenOfType>().First().Current.Value = 8); AddAssert("ensure setting is propagated", () => SelectedMods.Value.OfType().Single().CircleSize.Value == 8); @@ -235,7 +235,7 @@ namespace osu.Game.Tests.Visual.UserInterface AddStep("create second overlay", () => { - Add(modSelectScreen2 = new UserModSelectScreen().With(d => + Add(modSelectScreen2 = new UserModSelectOverlay().With(d => { d.Origin = Anchor.TopCentre; d.Anchor = Anchor.TopCentre; @@ -276,20 +276,20 @@ namespace osu.Game.Tests.Visual.UserInterface AddStep("Select all fun mods", () => { - modSelectScreen.ChildrenOfType() - .Single(c => c.ModType == ModType.DifficultyIncrease) - .SelectAll(); + modSelectOverlay.ChildrenOfType() + .Single(c => c.ModType == ModType.DifficultyIncrease) + .SelectAll(); }); AddUntilStep("many mods selected", () => SelectedMods.Value.Count >= 5); AddStep("trigger deselect and close overlay", () => { - modSelectScreen.ChildrenOfType() - .Single(c => c.ModType == ModType.DifficultyIncrease) - .DeselectAll(); + modSelectOverlay.ChildrenOfType() + .Single(c => c.ModType == ModType.DifficultyIncrease) + .DeselectAll(); - modSelectScreen.Hide(); + modSelectOverlay.Hide(); }); AddAssert("all mods deselected", () => SelectedMods.Value.Count == 0); @@ -378,15 +378,15 @@ namespace osu.Game.Tests.Visual.UserInterface createScreen(); changeRuleset(0); - AddAssert("double time visible", () => modSelectScreen.ChildrenOfType().Where(panel => panel.Mod is OsuModDoubleTime).Any(panel => !panel.Filtered.Value)); + AddAssert("double time visible", () => modSelectOverlay.ChildrenOfType().Where(panel => panel.Mod is OsuModDoubleTime).Any(panel => !panel.Filtered.Value)); - AddStep("make double time invalid", () => modSelectScreen.IsValidMod = m => !(m is OsuModDoubleTime)); - AddUntilStep("double time not visible", () => modSelectScreen.ChildrenOfType().Where(panel => panel.Mod is OsuModDoubleTime).All(panel => panel.Filtered.Value)); - AddAssert("nightcore still visible", () => modSelectScreen.ChildrenOfType().Where(panel => panel.Mod is OsuModNightcore).Any(panel => !panel.Filtered.Value)); + AddStep("make double time invalid", () => modSelectOverlay.IsValidMod = m => !(m is OsuModDoubleTime)); + AddUntilStep("double time not visible", () => modSelectOverlay.ChildrenOfType().Where(panel => panel.Mod is OsuModDoubleTime).All(panel => panel.Filtered.Value)); + AddAssert("nightcore still visible", () => modSelectOverlay.ChildrenOfType().Where(panel => panel.Mod is OsuModNightcore).Any(panel => !panel.Filtered.Value)); - AddStep("make double time valid again", () => modSelectScreen.IsValidMod = m => true); - AddUntilStep("double time visible", () => modSelectScreen.ChildrenOfType().Where(panel => panel.Mod is OsuModDoubleTime).Any(panel => !panel.Filtered.Value)); - AddAssert("nightcore still visible", () => modSelectScreen.ChildrenOfType().Where(b => b.Mod is OsuModNightcore).Any(panel => !panel.Filtered.Value)); + AddStep("make double time valid again", () => modSelectOverlay.IsValidMod = m => true); + AddUntilStep("double time visible", () => modSelectOverlay.ChildrenOfType().Where(panel => panel.Mod is OsuModDoubleTime).Any(panel => !panel.Filtered.Value)); + AddAssert("nightcore still visible", () => modSelectOverlay.ChildrenOfType().Where(b => b.Mod is OsuModNightcore).Any(panel => !panel.Filtered.Value)); } [Test] @@ -396,10 +396,10 @@ namespace osu.Game.Tests.Visual.UserInterface changeRuleset(0); AddStep("select DT + HD", () => SelectedMods.Value = new Mod[] { new OsuModDoubleTime(), new OsuModHidden() }); - AddAssert("DT + HD selected", () => modSelectScreen.ChildrenOfType().Count(panel => panel.Active.Value) == 2); + AddAssert("DT + HD selected", () => modSelectOverlay.ChildrenOfType().Count(panel => panel.Active.Value) == 2); - AddStep("make NF invalid", () => modSelectScreen.IsValidMod = m => !(m is ModNoFail)); - AddAssert("DT + HD still selected", () => modSelectScreen.ChildrenOfType().Count(panel => panel.Active.Value) == 2); + AddStep("make NF invalid", () => modSelectOverlay.IsValidMod = m => !(m is ModNoFail)); + AddAssert("DT + HD still selected", () => modSelectOverlay.ChildrenOfType().Count(panel => panel.Active.Value) == 2); } [Test] @@ -422,7 +422,7 @@ namespace osu.Game.Tests.Visual.UserInterface changeRuleset(0); AddStep("select DT + HD", () => SelectedMods.Value = new Mod[] { new OsuModDoubleTime(), new OsuModHidden() }); - AddAssert("DT + HD selected", () => modSelectScreen.ChildrenOfType().Count(panel => panel.Active.Value) == 2); + AddAssert("DT + HD selected", () => modSelectOverlay.ChildrenOfType().Count(panel => panel.Active.Value) == 2); AddStep("click deselect all button", () => { @@ -448,13 +448,13 @@ namespace osu.Game.Tests.Visual.UserInterface InputManager.MoveMouseTo(this.ChildrenOfType().First()); InputManager.Click(MouseButton.Left); }); - AddAssert("mod select hidden", () => modSelectScreen.State.Value == Visibility.Hidden); + AddAssert("mod select hidden", () => modSelectOverlay.State.Value == Visibility.Hidden); } [Test] public void TestColumnHiding() { - AddStep("create screen", () => Child = modSelectScreen = new UserModSelectScreen + AddStep("create screen", () => Child = modSelectOverlay = new UserModSelectOverlay { RelativeSizeAxes = Axes.Both, State = { Value = Visibility.Visible }, @@ -466,23 +466,23 @@ namespace osu.Game.Tests.Visual.UserInterface AddAssert("two columns visible", () => this.ChildrenOfType().Count(col => col.IsPresent) == 2); - AddStep("unset filter", () => modSelectScreen.IsValidMod = _ => true); + AddStep("unset filter", () => modSelectOverlay.IsValidMod = _ => true); AddAssert("all columns visible", () => this.ChildrenOfType().All(col => col.IsPresent)); - AddStep("filter out everything", () => modSelectScreen.IsValidMod = _ => false); + AddStep("filter out everything", () => modSelectOverlay.IsValidMod = _ => false); AddAssert("no columns visible", () => this.ChildrenOfType().All(col => !col.IsPresent)); - AddStep("hide", () => modSelectScreen.Hide()); - AddStep("set filter for 3 columns", () => modSelectScreen.IsValidMod = mod => mod.Type == ModType.DifficultyReduction - || mod.Type == ModType.Automation - || mod.Type == ModType.Conversion); + AddStep("hide", () => modSelectOverlay.Hide()); + AddStep("set filter for 3 columns", () => modSelectOverlay.IsValidMod = mod => mod.Type == ModType.DifficultyReduction + || mod.Type == ModType.Automation + || mod.Type == ModType.Conversion); - AddStep("show", () => modSelectScreen.Show()); + AddStep("show", () => modSelectOverlay.Show()); AddUntilStep("3 columns visible", () => this.ChildrenOfType().Count(col => col.IsPresent) == 3); } private void waitForColumnLoad() => AddUntilStep("all column content loaded", - () => modSelectScreen.ChildrenOfType().Any() && modSelectScreen.ChildrenOfType().All(column => column.IsLoaded && column.ItemsLoaded)); + () => modSelectOverlay.ChildrenOfType().Any() && modSelectOverlay.ChildrenOfType().All(column => column.IsLoaded && column.ItemsLoaded)); private void changeRuleset(int id) { @@ -492,14 +492,14 @@ namespace osu.Game.Tests.Visual.UserInterface private void assertCustomisationToggleState(bool disabled, bool active) { - ShearedToggleButton getToggle() => modSelectScreen.ChildrenOfType().Single(); + ShearedToggleButton getToggle() => modSelectOverlay.ChildrenOfType().Single(); AddAssert($"customisation toggle is {(disabled ? "" : "not ")}disabled", () => getToggle().Active.Disabled == disabled); AddAssert($"customisation toggle is {(active ? "" : "not ")}active", () => getToggle().Active.Value == active); } private ModPanel getPanelForMod(Type modType) - => modSelectScreen.ChildrenOfType().Single(panel => panel.Mod.GetType() == modType); + => modSelectOverlay.ChildrenOfType().Single(panel => panel.Mod.GetType() == modType); private class TestUnimplementedMod : Mod { diff --git a/osu.Game/Overlays/Mods/ModColumn.cs b/osu.Game/Overlays/Mods/ModColumn.cs index 3a2fda0bb0..b32ebb4a5c 100644 --- a/osu.Game/Overlays/Mods/ModColumn.cs +++ b/osu.Game/Overlays/Mods/ModColumn.cs @@ -384,7 +384,7 @@ namespace osu.Game.Overlays.Mods /// /// /// This method exists to be able to receive mod instances that come from potentially-external sources and to copy the changes across to this column's state. - /// uses this to substitute any external mod references in + /// uses this to substitute any external mod references in /// to references that are owned by this column. /// internal void SetSelection(IReadOnlyList mods) diff --git a/osu.Game/Overlays/Mods/ModSelectScreen.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs similarity index 99% rename from osu.Game/Overlays/Mods/ModSelectScreen.cs rename to osu.Game/Overlays/Mods/ModSelectOverlay.cs index 912c09f05c..b589b2e7e1 100644 --- a/osu.Game/Overlays/Mods/ModSelectScreen.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -27,7 +27,7 @@ using osuTK.Input; namespace osu.Game.Overlays.Mods { - public abstract class ModSelectScreen : ShearedOverlayContainer, ISamplePlaybackDisabler + public abstract class ModSelectOverlay : ShearedOverlayContainer, ISamplePlaybackDisabler { protected const int BUTTON_WIDTH = 200; @@ -76,7 +76,7 @@ namespace osu.Game.Overlays.Mods private ShearedToggleButton? customisationButton; - protected ModSelectScreen(OverlayColourScheme colourScheme = OverlayColourScheme.Green) + protected ModSelectOverlay(OverlayColourScheme colourScheme = OverlayColourScheme.Green) : base(colourScheme) { } diff --git a/osu.Game/Overlays/Mods/UserModSelectScreen.cs b/osu.Game/Overlays/Mods/UserModSelectOverlay.cs similarity index 92% rename from osu.Game/Overlays/Mods/UserModSelectScreen.cs rename to osu.Game/Overlays/Mods/UserModSelectOverlay.cs index a018797cba..8ff5e28c8f 100644 --- a/osu.Game/Overlays/Mods/UserModSelectScreen.cs +++ b/osu.Game/Overlays/Mods/UserModSelectOverlay.cs @@ -10,9 +10,9 @@ using osuTK.Input; namespace osu.Game.Overlays.Mods { - public class UserModSelectScreen : ModSelectScreen + public class UserModSelectOverlay : ModSelectOverlay { - public UserModSelectScreen(OverlayColourScheme colourScheme = OverlayColourScheme.Green) + public UserModSelectOverlay(OverlayColourScheme colourScheme = OverlayColourScheme.Green) : base(colourScheme) { } diff --git a/osu.Game/Screens/OnlinePlay/FreeModSelectScreen.cs b/osu.Game/Screens/OnlinePlay/FreeModSelectOverlay.cs similarity index 94% rename from osu.Game/Screens/OnlinePlay/FreeModSelectScreen.cs rename to osu.Game/Screens/OnlinePlay/FreeModSelectOverlay.cs index e92de5e083..6e1c9b7a59 100644 --- a/osu.Game/Screens/OnlinePlay/FreeModSelectScreen.cs +++ b/osu.Game/Screens/OnlinePlay/FreeModSelectOverlay.cs @@ -13,7 +13,7 @@ using osu.Game.Localisation; namespace osu.Game.Screens.OnlinePlay { - public class FreeModSelectScreen : ModSelectScreen + public class FreeModSelectOverlay : ModSelectOverlay { protected override bool ShowTotalMultiplier => false; @@ -23,7 +23,7 @@ namespace osu.Game.Screens.OnlinePlay set => base.IsValidMod = m => m.UserPlayable && value.Invoke(m); } - public FreeModSelectScreen() + public FreeModSelectOverlay() : base(OverlayColourScheme.Plum) { IsValidMod = _ => true; diff --git a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs index ec4e329f7a..13b2c37ded 100644 --- a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs @@ -81,7 +81,7 @@ namespace osu.Game.Screens.OnlinePlay.Match public readonly Room Room; private readonly bool allowEdit; - private ModSelectScreen userModsSelectOverlay; + private ModSelectOverlay userModsSelectOverlay; [CanBeNull] private IDisposable userModsSelectOverlayRegistration; @@ -231,7 +231,7 @@ namespace osu.Game.Screens.OnlinePlay.Match } }; - LoadComponent(userModsSelectOverlay = new UserModSelectScreen(OverlayColourScheme.Plum) + LoadComponent(userModsSelectOverlay = new UserModSelectOverlay(OverlayColourScheme.Plum) { SelectedMods = { BindTarget = UserMods }, IsValidMod = _ => false diff --git a/osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs b/osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs index c4503773ad..fb18a33d66 100644 --- a/osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs +++ b/osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs @@ -53,7 +53,7 @@ namespace osu.Game.Screens.OnlinePlay private IReadOnlyList initialMods; private bool itemSelected; - private readonly FreeModSelectScreen freeModSelectOverlay; + private readonly FreeModSelectOverlay freeModSelectOverlay; private IDisposable freeModSelectOverlayRegistration; protected OnlinePlaySongSelect(Room room) @@ -62,7 +62,7 @@ namespace osu.Game.Screens.OnlinePlay Padding = new MarginPadding { Horizontal = HORIZONTAL_OVERFLOW_PADDING }; - freeModSelectOverlay = new FreeModSelectScreen + freeModSelectOverlay = new FreeModSelectOverlay { SelectedMods = { BindTarget = FreeMods }, IsValidMod = IsValidFreeMod, @@ -160,7 +160,7 @@ namespace osu.Game.Screens.OnlinePlay return base.OnExiting(e); } - protected override ModSelectScreen CreateModSelectOverlay() => new UserModSelectScreen(OverlayColourScheme.Plum) + protected override ModSelectOverlay CreateModSelectOverlay() => new UserModSelectOverlay(OverlayColourScheme.Plum) { IsValidMod = IsValidMod }; diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index eb5e996972..8870239485 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -102,7 +102,7 @@ namespace osu.Game.Screens.Select [Resolved(CanBeNull = true)] private LegacyImportManager legacyImportManager { get; set; } - protected ModSelectScreen ModSelect { get; private set; } + protected ModSelectOverlay ModSelect { get; private set; } protected Sample SampleConfirm { get; private set; } @@ -333,7 +333,7 @@ namespace osu.Game.Screens.Select (new FooterButtonOptions(), BeatmapOptions) }; - protected virtual ModSelectScreen CreateModSelectOverlay() => new UserModSelectScreen(); + protected virtual ModSelectOverlay CreateModSelectOverlay() => new UserModSelectOverlay(); protected virtual void ApplyFilterToCarousel(FilterCriteria criteria) { From 9dce329e998e88c415c7d23576d75a29be3ca47f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 11 May 2022 10:38:35 +0900 Subject: [PATCH 096/147] Remove build suffix from version when reporting to sentry --- osu.Game/OsuGameBase.cs | 4 +++- osu.Game/Utils/SentryLogger.cs | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index ce798d4027..2e4758a134 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -86,6 +86,8 @@ namespace osu.Game public bool IsDeployedBuild => AssemblyVersion.Major > 0; + internal const string BUILD_SUFFIX = "lazer"; + public virtual string Version { get @@ -94,7 +96,7 @@ namespace osu.Game return @"local " + (DebugUtils.IsDebugBuild ? @"debug" : @"release"); var version = AssemblyVersion; - return $@"{version.Major}.{version.Minor}.{version.Build}-lazer"; + return $@"{version.Major}.{version.Minor}.{version.Build}-{BUILD_SUFFIX}"; } } diff --git a/osu.Game/Utils/SentryLogger.cs b/osu.Game/Utils/SentryLogger.cs index ad4bcf6274..218d10345f 100644 --- a/osu.Game/Utils/SentryLogger.cs +++ b/osu.Game/Utils/SentryLogger.cs @@ -34,7 +34,7 @@ namespace osu.Game.Utils options.AutoSessionTracking = true; options.IsEnvironmentUser = false; - options.Release = game.Version; + options.Release = game.Version.Replace($@"-{OsuGameBase.BUILD_SUFFIX}", string.Empty); }); Logger.NewEntry += processLogEntry; From 533f4b298f5f0a34fa690e33c11ac858cae783ee Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 11 May 2022 10:40:40 +0900 Subject: [PATCH 097/147] Add explanation inline for future visitors --- osu.Game/Utils/SentryLogger.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Utils/SentryLogger.cs b/osu.Game/Utils/SentryLogger.cs index 218d10345f..16f1a8b039 100644 --- a/osu.Game/Utils/SentryLogger.cs +++ b/osu.Game/Utils/SentryLogger.cs @@ -34,6 +34,8 @@ namespace osu.Game.Utils options.AutoSessionTracking = true; options.IsEnvironmentUser = false; + // The reported release needs to match release tags on github in order for sentry + // to automatically associate and track against releases. options.Release = game.Version.Replace($@"-{OsuGameBase.BUILD_SUFFIX}", string.Empty); }); From 843e13a471a0b763ca5cd2f6047478c1fb0241de Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 11 May 2022 12:55:15 +0900 Subject: [PATCH 098/147] Add screen stack context to sentry --- osu.Game/OsuGame.cs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index b8abef38a8..9edcf90132 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -56,6 +56,8 @@ using osu.Game.Updater; using osu.Game.Users; using osu.Game.Utils; using osuTK.Graphics; +using Sentry; +using Logger = osu.Framework.Logging.Logger; namespace osu.Game { @@ -1197,6 +1199,15 @@ namespace osu.Game private void screenChanged(IScreen current, IScreen newScreen) { + SentrySdk.ConfigureScope(scope => + { + scope.Contexts[@"screen stack"] = new + { + Current = newScreen.GetType().Name, + Previous = current.GetType().Name, + }; + }); + switch (newScreen) { case IntroScreen intro: From b136677bb002565e7736f7947a44fd456685d5df Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 11 May 2022 14:03:16 +0900 Subject: [PATCH 099/147] Add config and clock time contexts to sentry --- osu.Game/Configuration/OsuConfigManager.cs | 16 ++++++++++++++++ osu.Game/Utils/SentryLogger.cs | 18 ++++++++++++++++++ 2 files changed, 34 insertions(+) diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index 64e77384a2..026a83cceb 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -2,8 +2,10 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Collections.Generic; using System.Diagnostics; using System.Globalization; +using System.Linq; using osu.Framework.Configuration; using osu.Framework.Configuration.Tracking; using osu.Framework.Extensions; @@ -164,6 +166,20 @@ namespace osu.Game.Configuration SetDefault(OsuSetting.EditorHitAnimations, false); } + public IDictionary GetLoggableState() => + new Dictionary(ConfigStore.Where(kvp => !keyContainsPrivateInformation(kvp.Key)).ToDictionary(kvp => kvp.Key, kvp => kvp.Value.ToString())); + + private static bool keyContainsPrivateInformation(OsuSetting argKey) + { + switch (argKey) + { + case OsuSetting.Token: + return true; + } + + return false; + } + public OsuConfigManager(Storage storage) : base(storage) { diff --git a/osu.Game/Utils/SentryLogger.cs b/osu.Game/Utils/SentryLogger.cs index ad4bcf6274..e019e4bb12 100644 --- a/osu.Game/Utils/SentryLogger.cs +++ b/osu.Game/Utils/SentryLogger.cs @@ -7,9 +7,12 @@ using System; using System.Diagnostics; using System.IO; using System.Net; +using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Logging; +using osu.Game.Configuration; using osu.Game.Online.API.Requests.Responses; +using osu.Game.Overlays; using Sentry; using Sentry.Protocol; @@ -24,8 +27,11 @@ namespace osu.Game.Utils private readonly IDisposable? sentrySession; + private readonly OsuGame game; + public SentryLogger(OsuGame game) { + this.game = game; sentrySession = SentrySdk.Init(options => { // Not setting the dsn will completely disable sentry. @@ -92,6 +98,18 @@ namespace osu.Game.Utils { Message = entry.Message, Level = getSentryLevel(entry.Level), + }, scope => + { + scope.Contexts[@"config"] = new + { + Game = game.Dependencies.Get().GetLoggableState() + // TODO: add framework config here. needs some consideration on how to expose. + }; + scope.Contexts[@"clocks"] = new + { + Audio = game.Dependencies.Get().CurrentTrack.CurrentTime, + Game = game.Clock.CurrentTime, + }; }); } else From 977a0453cc3e18bbc38ba75d77288b0af72834c3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 11 May 2022 14:11:20 +0900 Subject: [PATCH 100/147] Add beatmap context to sentry --- osu.Game/Utils/SentryLogger.cs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/osu.Game/Utils/SentryLogger.cs b/osu.Game/Utils/SentryLogger.cs index e019e4bb12..d02ffcb013 100644 --- a/osu.Game/Utils/SentryLogger.cs +++ b/osu.Game/Utils/SentryLogger.cs @@ -10,6 +10,7 @@ using System.Net; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Logging; +using osu.Game.Beatmaps; using osu.Game.Configuration; using osu.Game.Online.API.Requests.Responses; using osu.Game.Overlays; @@ -105,6 +106,15 @@ namespace osu.Game.Utils Game = game.Dependencies.Get().GetLoggableState() // TODO: add framework config here. needs some consideration on how to expose. }; + + var beatmap = game.Dependencies.Get>().Value.BeatmapInfo; + + scope.Contexts[@"beatmap"] = new + { + Name = beatmap.ToString(), + beatmap.OnlineID, + }; + scope.Contexts[@"clocks"] = new { Audio = game.Dependencies.Get().CurrentTrack.CurrentTime, From f53d42d31ff51e6c43878e49f2db6b3da73a5841 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 11 May 2022 14:51:56 +0900 Subject: [PATCH 101/147] Add realm context to sentry --- osu.Game/Utils/SentryLogger.cs | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/osu.Game/Utils/SentryLogger.cs b/osu.Game/Utils/SentryLogger.cs index d02ffcb013..a291cee9ae 100644 --- a/osu.Game/Utils/SentryLogger.cs +++ b/osu.Game/Utils/SentryLogger.cs @@ -6,14 +6,18 @@ using System; using System.Diagnostics; using System.IO; +using System.Linq; using System.Net; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Logging; using osu.Game.Beatmaps; using osu.Game.Configuration; +using osu.Game.Database; +using osu.Game.Models; using osu.Game.Online.API.Requests.Responses; using osu.Game.Overlays; +using osu.Game.Skinning; using Sentry; using Sentry.Protocol; @@ -101,13 +105,27 @@ namespace osu.Game.Utils Level = getSentryLevel(entry.Level), }, scope => { + var beatmap = game.Dependencies.Get>().Value.BeatmapInfo; + scope.Contexts[@"config"] = new { Game = game.Dependencies.Get().GetLoggableState() // TODO: add framework config here. needs some consideration on how to expose. }; - var beatmap = game.Dependencies.Get>().Value.BeatmapInfo; + game.Dependencies.Get().Run(realm => + { + scope.Contexts[@"realm"] = new + { + Counts = new + { + BeatmapSets = realm.All().Count(), + Beatmaps = realm.All().Count(), + Files = realm.All().Count(), + Skins = realm.All().Count(), + } + }; + }); scope.Contexts[@"beatmap"] = new { From be09ec833e0b5e271f6fe8d50dd3d0e4d965de30 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 11 May 2022 14:52:08 +0900 Subject: [PATCH 102/147] Add global statistics context to sentry --- osu.Game/Utils/SentryLogger.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/osu.Game/Utils/SentryLogger.cs b/osu.Game/Utils/SentryLogger.cs index a291cee9ae..d68f43e88a 100644 --- a/osu.Game/Utils/SentryLogger.cs +++ b/osu.Game/Utils/SentryLogger.cs @@ -11,6 +11,7 @@ using System.Net; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Logging; +using osu.Framework.Statistics; using osu.Game.Beatmaps; using osu.Game.Configuration; using osu.Game.Database; @@ -127,6 +128,10 @@ namespace osu.Game.Utils }; }); + scope.Contexts[@"global statistics"] = GlobalStatistics.GetStatistics() + .GroupBy(s => s.Group) + .ToDictionary(g => g.Key, items => items.ToDictionary(i => i.Name, g => g.DisplayValue)); + scope.Contexts[@"beatmap"] = new { Name = beatmap.ToString(), From 0b597e712ed72de537fc9e8e413bc06a7c477111 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 11 May 2022 09:39:27 +0300 Subject: [PATCH 103/147] Fix timeline not handling mouse down events --- .../Screens/Edit/Compose/Components/Timeline/Timeline.cs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs index 51b8792d87..7e66c57917 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs @@ -270,12 +270,9 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline protected override bool OnMouseDown(MouseDownEvent e) { if (base.OnMouseDown(e)) - { beginUserDrag(); - return true; - } - return false; + return true; } protected override void OnMouseUp(MouseUpEvent e) From e43ce28ada3a21a1b7c978da2db16ea9fee0f994 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 11 May 2022 09:51:00 +0300 Subject: [PATCH 104/147] Remove references of "difficulty point" in sample point test scene Fat-fingered. --- .../Editing/TestSceneHitObjectSamplePointAdjustments.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneHitObjectSamplePointAdjustments.cs b/osu.Game.Tests/Visual/Editing/TestSceneHitObjectSamplePointAdjustments.cs index dca30a6fc0..f0a2347c31 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneHitObjectSamplePointAdjustments.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneHitObjectSamplePointAdjustments.cs @@ -173,11 +173,11 @@ namespace osu.Game.Tests.Visual.Editing samplePopoverHasSingleBank("normal"); } - private void clickSamplePiece(int objectIndex) => AddStep($"click {objectIndex.ToOrdinalWords()} difficulty piece", () => + private void clickSamplePiece(int objectIndex) => AddStep($"click {objectIndex.ToOrdinalWords()} sample piece", () => { - var difficultyPiece = this.ChildrenOfType().Single(piece => piece.HitObject == EditorBeatmap.HitObjects.ElementAt(objectIndex)); + var samplePiece = this.ChildrenOfType().Single(piece => piece.HitObject == EditorBeatmap.HitObjects.ElementAt(objectIndex)); - InputManager.MoveMouseTo(difficultyPiece); + InputManager.MoveMouseTo(samplePiece); InputManager.Click(MouseButton.Left); }); @@ -216,7 +216,7 @@ namespace osu.Game.Tests.Visual.Editing private void dismissPopover() { AddStep("dismiss popover", () => InputManager.Key(Key.Escape)); - AddUntilStep("wait for dismiss", () => !this.ChildrenOfType().Any(popover => popover.IsPresent)); + AddUntilStep("wait for dismiss", () => !this.ChildrenOfType().Any(popover => popover.IsPresent)); } private void setVolumeViaPopover(int volume) => AddStep($"set volume {volume} via popover", () => From f797514bce5aafb5df020e7fa62c2dd24b0e3139 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 11 May 2022 09:51:59 +0300 Subject: [PATCH 105/147] Add failing test cases --- ...tSceneHitObjectDifficultyPointAdjustments.cs | 17 +++++++++++++++++ .../TestSceneHitObjectSamplePointAdjustments.cs | 16 ++++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneHitObjectDifficultyPointAdjustments.cs b/osu.Game.Tests/Visual/Editing/TestSceneHitObjectDifficultyPointAdjustments.cs index 4012a672ed..7f82d5966e 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneHitObjectDifficultyPointAdjustments.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneHitObjectDifficultyPointAdjustments.cs @@ -7,6 +7,7 @@ using NUnit.Framework; using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Graphics.UserInterface; using osu.Game.Rulesets; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Osu; @@ -66,6 +67,13 @@ namespace osu.Game.Tests.Visual.Editing }); } + [Test] + public void TestPopoverHasFocus() + { + clickDifficultyPiece(0); + velocityPopoverHasFocus(); + } + [Test] public void TestSingleSelection() { @@ -133,6 +141,15 @@ namespace osu.Game.Tests.Visual.Editing InputManager.Click(MouseButton.Left); }); + private void velocityPopoverHasFocus() => AddUntilStep("velocity popover textbox focused", () => + { + var popover = this.ChildrenOfType().SingleOrDefault(); + var slider = popover?.ChildrenOfType>().Single(); + var textbox = slider?.ChildrenOfType().Single(); + + return textbox?.HasFocus == true; + }); + private void velocityPopoverHasSingleValue(double velocity) => AddUntilStep($"velocity popover has {velocity}", () => { var popover = this.ChildrenOfType().SingleOrDefault(); diff --git a/osu.Game.Tests/Visual/Editing/TestSceneHitObjectSamplePointAdjustments.cs b/osu.Game.Tests/Visual/Editing/TestSceneHitObjectSamplePointAdjustments.cs index f0a2347c31..fcdf4a6e33 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneHitObjectSamplePointAdjustments.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneHitObjectSamplePointAdjustments.cs @@ -57,6 +57,13 @@ namespace osu.Game.Tests.Visual.Editing }); } + [Test] + public void TestPopoverHasFocus() + { + clickSamplePiece(0); + samplePopoverHasFocus(); + } + [Test] public void TestSingleSelection() { @@ -181,6 +188,15 @@ namespace osu.Game.Tests.Visual.Editing InputManager.Click(MouseButton.Left); }); + private void samplePopoverHasFocus() => AddUntilStep("sample popover textbox focused", () => + { + var popover = this.ChildrenOfType().SingleOrDefault(); + var slider = popover?.ChildrenOfType>().Single(); + var textbox = slider?.ChildrenOfType().Single(); + + return textbox?.HasFocus == true; + }); + private void samplePopoverHasSingleVolume(int volume) => AddUntilStep($"sample popover has volume {volume}", () => { var popover = this.ChildrenOfType().SingleOrDefault(); From 24432dffc4622135e1a296638d1e765ca9801477 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 11 May 2022 09:53:04 +0300 Subject: [PATCH 106/147] Add support for focusing intermediate slider-textbox component --- .../Edit/Timing/IndeterminateSliderWithTextBoxInput.cs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/osu.Game/Screens/Edit/Timing/IndeterminateSliderWithTextBoxInput.cs b/osu.Game/Screens/Edit/Timing/IndeterminateSliderWithTextBoxInput.cs index 0cf2cf6c54..16a04982f5 100644 --- a/osu.Game/Screens/Edit/Timing/IndeterminateSliderWithTextBoxInput.cs +++ b/osu.Game/Screens/Edit/Timing/IndeterminateSliderWithTextBoxInput.cs @@ -7,6 +7,7 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.UserInterface; +using osu.Framework.Input.Events; using osu.Framework.Localisation; using osu.Game.Graphics.UserInterfaceV2; using osu.Game.Overlays.Settings; @@ -107,6 +108,14 @@ namespace osu.Game.Screens.Edit.Timing Current.BindValueChanged(_ => updateState(), true); } + public override bool AcceptsFocus => true; + + protected override void OnFocus(FocusEvent e) + { + base.OnFocus(e); + GetContainingInputManager().ChangeFocus(textBox); + } + private void updateState() { if (Current.Value is T nonNullValue) From 96db530de63626afa8ed51f3f8f24de669a7faa2 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 11 May 2022 09:55:34 +0300 Subject: [PATCH 107/147] Focus textbox on control point popovers --- .../Compose/Components/Timeline/DifficultyPointPiece.cs | 6 ++++++ .../Edit/Compose/Components/Timeline/SamplePointPiece.cs | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/DifficultyPointPiece.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/DifficultyPointPiece.cs index b230bab0c2..eaaa663fe7 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/DifficultyPointPiece.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/DifficultyPointPiece.cs @@ -122,6 +122,12 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline beatmap.EndChange(); }); } + + protected override void LoadComplete() + { + base.LoadComplete(); + ScheduleAfterChildren(() => GetContainingInputManager().ChangeFocus(sliderVelocitySlider)); + } } } } diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs index fc0952d4f0..ab21a83c43 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs @@ -126,6 +126,12 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline volume.Current.BindValueChanged(val => updateVolumeFor(relevantObjects, val.NewValue)); } + protected override void LoadComplete() + { + base.LoadComplete(); + ScheduleAfterChildren(() => GetContainingInputManager().ChangeFocus(volume)); + } + private static string? getCommonBank(SampleControlPoint[] relevantControlPoints) => relevantControlPoints.Select(point => point.SampleBank).Distinct().Count() == 1 ? relevantControlPoints.First().SampleBank : null; private static int? getCommonVolume(SampleControlPoint[] relevantControlPoints) => relevantControlPoints.Select(point => point.SampleVolume).Distinct().Count() == 1 ? (int?)relevantControlPoints.First().SampleVolume : null; From 1c369956066c28ffe2333d03ae9353e1cf9a09f9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 11 May 2022 16:09:16 +0900 Subject: [PATCH 108/147] Expose `HoldingForHUD` state from `HUDOverlay` as bindable --- osu.Game/Screens/Play/HUDOverlay.cs | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index abfed1acd0..f6087e0958 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -68,7 +68,9 @@ namespace osu.Game.Screens.Play internal readonly IBindable IsPlaying = new Bindable(); - private bool holdingForHUD; + public IBindable HoldingForHUD => holdingForHUD; + + private readonly BindableBool holdingForHUD = new BindableBool(); private readonly SkinnableTargetContainer mainComponents; @@ -144,7 +146,8 @@ namespace osu.Game.Screens.Play hideTargets.ForEach(d => d.Hide()); } - public override void Hide() => throw new InvalidOperationException($"{nameof(HUDOverlay)} should not be hidden as it will remove the ability of a user to quit. Use {nameof(ShowHud)} instead."); + public override void Hide() => + throw new InvalidOperationException($"{nameof(HUDOverlay)} should not be hidden as it will remove the ability of a user to quit. Use {nameof(ShowHud)} instead."); protected override void LoadComplete() { @@ -152,6 +155,7 @@ namespace osu.Game.Screens.Play ShowHud.BindValueChanged(visible => hideTargets.ForEach(d => d.FadeTo(visible.NewValue ? 1 : 0, FADE_DURATION, FADE_EASING))); + holdingForHUD.BindValueChanged(_ => updateVisibility()); IsPlaying.BindValueChanged(_ => updateVisibility()); configVisibilityMode.BindValueChanged(_ => updateVisibility(), true); @@ -204,7 +208,7 @@ namespace osu.Game.Screens.Play if (ShowHud.Disabled) return; - if (holdingForHUD) + if (holdingForHUD.Value) { ShowHud.Value = true; return; @@ -287,8 +291,7 @@ namespace osu.Game.Screens.Play switch (e.Action) { case GlobalAction.HoldForHUD: - holdingForHUD = true; - updateVisibility(); + holdingForHUD.Value = true; return true; case GlobalAction.ToggleInGameInterface: @@ -318,8 +321,7 @@ namespace osu.Game.Screens.Play switch (e.Action) { case GlobalAction.HoldForHUD: - holdingForHUD = false; - updateVisibility(); + holdingForHUD.Value = false; break; } } From d05cd6908763cd47ca66281f70d6540b2080ea39 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 11 May 2022 16:12:54 +0900 Subject: [PATCH 109/147] Change multiplayer leaderboard to always hide during gameplay unless holding-for-HUD --- .../OnlinePlay/Multiplayer/MultiplayerPlayer.cs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs index 02ff040a94..5dab845999 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs @@ -43,6 +43,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer private readonly MultiplayerRoomUser[] users; + private readonly Bindable leaderboardExpanded = new BindableBool(); + private LoadingLayer loadingDisplay; private FillFlowContainer leaderboardFlow; @@ -76,13 +78,16 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer Spacing = new Vector2(5) }); + HUDOverlay.HoldingForHUD.BindValueChanged(_ => updateLeaderboardExpandedState()); + LocalUserPlaying.BindValueChanged(_ => updateLeaderboardExpandedState(), true); + // todo: this should be implemented via a custom HUD implementation, and correctly masked to the main content area. LoadComponentAsync(leaderboard = new MultiplayerGameplayLeaderboard(GameplayState.Ruleset.RulesetInfo, ScoreProcessor, users), l => { if (!LoadedBeatmapSuccessfully) return; - ((IBindable)leaderboard.Expanded).BindTo(HUDOverlay.ShowHud); + leaderboard.Expanded.BindTo(leaderboardExpanded); leaderboardFlow.Insert(0, l); @@ -99,7 +104,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer LoadComponentAsync(new GameplayChatDisplay(Room) { - Expanded = { BindTarget = HUDOverlay.ShowHud }, + Expanded = { BindTarget = leaderboardExpanded }, }, chat => leaderboardFlow.Insert(2, chat)); HUDOverlay.Add(loadingDisplay = new LoadingLayer(true) { Depth = float.MaxValue }); @@ -152,6 +157,9 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer } } + private void updateLeaderboardExpandedState() => + leaderboardExpanded.Value = !LocalUserPlaying.Value || HUDOverlay.HoldingForHUD.Value; + private void failAndBail(string message = null) { if (!string.IsNullOrEmpty(message)) From d51689e9ae38a0f0030ac436701d22d57465b51d Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 11 May 2022 11:25:41 +0300 Subject: [PATCH 110/147] Unfocus textbox when dismissing popover in test scene --- .../Editing/TestSceneHitObjectDifficultyPointAdjustments.cs | 1 + .../Visual/Editing/TestSceneHitObjectSamplePointAdjustments.cs | 1 + 2 files changed, 2 insertions(+) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneHitObjectDifficultyPointAdjustments.cs b/osu.Game.Tests/Visual/Editing/TestSceneHitObjectDifficultyPointAdjustments.cs index 7f82d5966e..7c05abc2cd 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneHitObjectDifficultyPointAdjustments.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneHitObjectDifficultyPointAdjustments.cs @@ -168,6 +168,7 @@ namespace osu.Game.Tests.Visual.Editing private void dismissPopover() { + AddStep("unfocus textbox", () => InputManager.Key(Key.Escape)); AddStep("dismiss popover", () => InputManager.Key(Key.Escape)); AddUntilStep("wait for dismiss", () => !this.ChildrenOfType().Any(popover => popover.IsPresent)); } diff --git a/osu.Game.Tests/Visual/Editing/TestSceneHitObjectSamplePointAdjustments.cs b/osu.Game.Tests/Visual/Editing/TestSceneHitObjectSamplePointAdjustments.cs index fcdf4a6e33..4501eea88e 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneHitObjectSamplePointAdjustments.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneHitObjectSamplePointAdjustments.cs @@ -231,6 +231,7 @@ namespace osu.Game.Tests.Visual.Editing private void dismissPopover() { + AddStep("unfocus textbox", () => InputManager.Key(Key.Escape)); AddStep("dismiss popover", () => InputManager.Key(Key.Escape)); AddUntilStep("wait for dismiss", () => !this.ChildrenOfType().Any(popover => popover.IsPresent)); } From 551370d27b1a9c4e941335e706efc9bc19896cfe Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 11 May 2022 18:26:08 +0900 Subject: [PATCH 111/147] Update ReSharper package --- .config/dotnet-tools.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json index 1132396608..65ac05261a 100644 --- a/.config/dotnet-tools.json +++ b/.config/dotnet-tools.json @@ -3,7 +3,7 @@ "isRoot": true, "tools": { "jetbrains.resharper.globaltools": { - "version": "2022.1.0-eap10", + "version": "2022.1.1", "commands": [ "jb" ] @@ -27,4 +27,4 @@ ] } } -} +} \ No newline at end of file From a46894b613df38cb0d4d159f6e86a87dd2a0e8fb Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 11 May 2022 18:27:57 +0900 Subject: [PATCH 112/147] Bust CI cache on more files --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 514acef525..729f2f266d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -31,7 +31,7 @@ jobs: uses: actions/cache@v3 with: path: ${{ github.workspace }}/inspectcode - key: inspectcode-${{ hashFiles('.config/dotnet-tools.json') }}-${{ hashFiles('.github/workflows/ci.yml' ) }} + key: inspectcode-${{ hashFiles('.config/dotnet-tools.json', '.github/workflows/ci.yml', 'osu.sln*', '.editorconfig', '.globalconfig') }} - name: Dotnet code style run: dotnet build -c Debug -warnaserror osu.Desktop.slnf -p:EnforceCodeStyleInBuild=true From fecf92e16f926b2d5e2a8f51b2e3c03bf8736d0d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 11 May 2022 19:51:22 +0900 Subject: [PATCH 113/147] Update libraries to latest versions --- .../osu.Game.Rulesets.EmptyFreeform.Tests.csproj | 2 +- .../osu.Game.Rulesets.Pippidon.Tests.csproj | 2 +- .../osu.Game.Rulesets.EmptyScrolling.Tests.csproj | 2 +- .../osu.Game.Rulesets.Pippidon.Tests.csproj | 2 +- osu.Android.props | 2 +- osu.Desktop/osu.Desktop.csproj | 2 +- osu.Game.Benchmarks/osu.Game.Benchmarks.csproj | 2 +- .../osu.Game.Rulesets.Catch.Tests.csproj | 2 +- .../osu.Game.Rulesets.Mania.Tests.csproj | 2 +- .../osu.Game.Rulesets.Osu.Tests.csproj | 2 +- .../osu.Game.Rulesets.Taiko.Tests.csproj | 2 +- osu.Game.Tests/osu.Game.Tests.csproj | 2 +- .../osu.Game.Tournament.Tests.csproj | 2 +- osu.Game/osu.Game.csproj | 14 +++++++------- osu.iOS.props | 8 ++++---- 15 files changed, 24 insertions(+), 24 deletions(-) diff --git a/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/osu.Game.Rulesets.EmptyFreeform.Tests.csproj b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/osu.Game.Rulesets.EmptyFreeform.Tests.csproj index cb922c5a58..bc285dbe11 100644 --- a/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/osu.Game.Rulesets.EmptyFreeform.Tests.csproj +++ b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/osu.Game.Rulesets.EmptyFreeform.Tests.csproj @@ -11,7 +11,7 @@ - + diff --git a/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj index 5ecd9cc675..718ada1905 100644 --- a/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj +++ b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj @@ -11,7 +11,7 @@ - + diff --git a/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/osu.Game.Rulesets.EmptyScrolling.Tests.csproj b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/osu.Game.Rulesets.EmptyScrolling.Tests.csproj index 33ad0ac4f7..6b9c3f4d63 100644 --- a/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/osu.Game.Rulesets.EmptyScrolling.Tests.csproj +++ b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/osu.Game.Rulesets.EmptyScrolling.Tests.csproj @@ -11,7 +11,7 @@ - + diff --git a/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj index 5ecd9cc675..718ada1905 100644 --- a/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj +++ b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj @@ -11,7 +11,7 @@ - + diff --git a/osu.Android.props b/osu.Android.props index 97d9dbc380..af4a93d191 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -56,6 +56,6 @@ - + diff --git a/osu.Desktop/osu.Desktop.csproj b/osu.Desktop/osu.Desktop.csproj index a4f309c6ac..a4f9e2671b 100644 --- a/osu.Desktop/osu.Desktop.csproj +++ b/osu.Desktop/osu.Desktop.csproj @@ -24,7 +24,7 @@ - + diff --git a/osu.Game.Benchmarks/osu.Game.Benchmarks.csproj b/osu.Game.Benchmarks/osu.Game.Benchmarks.csproj index 434c0e0367..36ffd3b5b6 100644 --- a/osu.Game.Benchmarks/osu.Game.Benchmarks.csproj +++ b/osu.Game.Benchmarks/osu.Game.Benchmarks.csproj @@ -8,7 +8,7 @@ - + diff --git a/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj b/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj index fc6d900567..b957ade952 100644 --- a/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj +++ b/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj @@ -3,7 +3,7 @@ - + diff --git a/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj b/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj index ddad2adfea..d3b4b378c0 100644 --- a/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj +++ b/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj @@ -3,7 +3,7 @@ - + diff --git a/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj b/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj index 4ce29ab5c7..2c0d3fd937 100644 --- a/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj +++ b/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj @@ -4,7 +4,7 @@ - + diff --git a/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj b/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj index a6b8eb8651..ce468d399b 100644 --- a/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj +++ b/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj @@ -3,7 +3,7 @@ - + diff --git a/osu.Game.Tests/osu.Game.Tests.csproj b/osu.Game.Tests/osu.Game.Tests.csproj index 0bcf533653..a1eef4ce47 100644 --- a/osu.Game.Tests/osu.Game.Tests.csproj +++ b/osu.Game.Tests/osu.Game.Tests.csproj @@ -5,7 +5,7 @@ - + diff --git a/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj b/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj index c7314a4969..6fd53d923b 100644 --- a/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj +++ b/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj @@ -6,7 +6,7 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 2f32c843c0..89cb882981 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -23,9 +23,9 @@ - - - + + + @@ -34,12 +34,12 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - + - - - + + + diff --git a/osu.iOS.props b/osu.iOS.props index b483267696..46580705f7 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -85,10 +85,10 @@ - - + + - - + + From 3cbd19a9ce09c800005e6b51d263e4d4593362d5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 11 May 2022 21:17:16 +0900 Subject: [PATCH 114/147] 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 af4a93d191..98dc28d915 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 89cb882981..772a78c8fe 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -35,7 +35,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/osu.iOS.props b/osu.iOS.props index 46580705f7..af8f9b617c 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -61,7 +61,7 @@ - + @@ -84,7 +84,7 @@ - + From 9d30b490ade53ada8771a704a03163c04f15028f Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 11 May 2022 18:26:04 +0300 Subject: [PATCH 115/147] Fix intermittent test failures in results screen --- .../Visual/Ranking/TestSceneResultsScreen.cs | 32 ++++++++++++++----- 1 file changed, 24 insertions(+), 8 deletions(-) diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs b/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs index a1d51683e4..2a5fc050d3 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs @@ -103,7 +103,7 @@ namespace osu.Game.Tests.Visual.Ranking score.Accuracy = accuracy; score.Rank = rank; - AddStep("load results", () => Child = new TestResultsContainer(screen = createResultsScreen(score))); + loadResultsScreen(() => screen = createResultsScreen(score)); AddUntilStep("wait for loaded", () => screen.IsLoaded); AddAssert("retry overlay present", () => screen.RetryOverlay != null); } @@ -113,7 +113,7 @@ namespace osu.Game.Tests.Visual.Ranking { UnrankedSoloResultsScreen screen = null; - AddStep("load results", () => Child = new TestResultsContainer(screen = createUnrankedSoloResultsScreen())); + loadResultsScreen(() => screen = createUnrankedSoloResultsScreen()); AddUntilStep("wait for loaded", () => screen.IsLoaded); AddAssert("retry overlay present", () => screen.RetryOverlay != null); } @@ -123,7 +123,7 @@ namespace osu.Game.Tests.Visual.Ranking { TestResultsScreen screen = null; - AddStep("load results", () => Child = new TestResultsContainer(screen = createResultsScreen())); + loadResultsScreen(() => screen = createResultsScreen()); AddUntilStep("wait for load", () => this.ChildrenOfType().Single().AllPanelsVisible); AddStep("click expanded panel", () => @@ -162,7 +162,7 @@ namespace osu.Game.Tests.Visual.Ranking { TestResultsScreen screen = null; - AddStep("load results", () => Child = new TestResultsContainer(screen = createResultsScreen())); + loadResultsScreen(() => screen = createResultsScreen()); AddUntilStep("wait for load", () => this.ChildrenOfType().Single().AllPanelsVisible); AddStep("click expanded panel", () => @@ -201,7 +201,7 @@ namespace osu.Game.Tests.Visual.Ranking { TestResultsScreen screen = null; - AddStep("load results", () => Child = new TestResultsContainer(screen = createResultsScreen())); + loadResultsScreen(() => screen = createResultsScreen()); AddUntilStep("wait for load", () => this.ChildrenOfType().Single().AllPanelsVisible); ScorePanel expandedPanel = null; @@ -231,7 +231,7 @@ namespace osu.Game.Tests.Visual.Ranking var tcs = new TaskCompletionSource(); - AddStep("load results", () => Child = new TestResultsContainer(screen = new DelayedFetchResultsScreen(TestResources.CreateTestScoreInfo(), tcs.Task))); + loadResultsScreen(() => screen = new DelayedFetchResultsScreen(TestResources.CreateTestScoreInfo(), tcs.Task)); AddUntilStep("wait for loaded", () => screen.IsLoaded); @@ -255,7 +255,7 @@ namespace osu.Game.Tests.Visual.Ranking { TestResultsScreen screen = null; - AddStep("load results", () => Child = new TestResultsContainer(screen = createResultsScreen())); + loadResultsScreen(() => screen = createResultsScreen()); AddUntilStep("wait for load", () => this.ChildrenOfType().Single().AllPanelsVisible); AddAssert("download button is disabled", () => !screen.ChildrenOfType().Last().Enabled.Value); @@ -276,7 +276,7 @@ namespace osu.Game.Tests.Visual.Ranking var ruleset = new RulesetWithNoPerformanceCalculator(); var score = TestResources.CreateTestScoreInfo(ruleset.RulesetInfo); - AddStep("load results", () => Child = new TestResultsContainer(createResultsScreen(score))); + loadResultsScreen(() => createResultsScreen(score)); AddUntilStep("wait for load", () => this.ChildrenOfType().Single().AllPanelsVisible); AddAssert("PP displayed as 0", () => @@ -287,6 +287,22 @@ namespace osu.Game.Tests.Visual.Ranking }); } + private void loadResultsScreen(Func createResults) + { + ResultsScreen results = null; + + AddStep("load results", () => Child = new TestResultsContainer(results = createResults())); + + // expanded panel should be centered the moment results screen is loaded + // but can potentially be scrolled away on certain specific load scenarios. + // see: https://github.com/ppy/osu/issues/18226 + AddUntilStep("expanded panel in centre of screen", () => + { + var expandedPanel = this.ChildrenOfType().Single(p => p.State == PanelState.Expanded); + return Precision.AlmostEquals(expandedPanel.ScreenSpaceDrawQuad.Centre.X, results.ScreenSpaceDrawQuad.Centre.X, 1); + }); + } + private TestResultsScreen createResultsScreen(ScoreInfo score = null) => new TestResultsScreen(score ?? TestResources.CreateTestScoreInfo()); private UnrankedSoloResultsScreen createUnrankedSoloResultsScreen() => new UnrankedSoloResultsScreen(TestResources.CreateTestScoreInfo()); From a104277e7fbd1315fa230fa67d6be9ea317f92e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 11 May 2022 18:01:33 +0200 Subject: [PATCH 116/147] Rename `ModSelect{Screen -> Overlay}Strings` --- ...ModSelectScreenStrings.cs => ModSelectOverlayStrings.cs} | 6 +++--- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) rename osu.Game/Localisation/{ModSelectScreenStrings.cs => ModSelectOverlayStrings.cs} (94%) diff --git a/osu.Game/Localisation/ModSelectScreenStrings.cs b/osu.Game/Localisation/ModSelectOverlayStrings.cs similarity index 94% rename from osu.Game/Localisation/ModSelectScreenStrings.cs rename to osu.Game/Localisation/ModSelectOverlayStrings.cs index 0c113fd381..e9af7147e3 100644 --- a/osu.Game/Localisation/ModSelectScreenStrings.cs +++ b/osu.Game/Localisation/ModSelectOverlayStrings.cs @@ -5,9 +5,9 @@ using osu.Framework.Localisation; namespace osu.Game.Localisation { - public static class ModSelectScreenStrings + public static class ModSelectOverlayStrings { - private const string prefix = @"osu.Game.Resources.Localisation.ModSelectScreen"; + private const string prefix = @"osu.Game.Resources.Localisation.ModSelectOverlay"; /// /// "Mod Select" @@ -26,4 +26,4 @@ namespace osu.Game.Localisation private static string getKey(string key) => $@"{prefix}:{key}"; } -} \ No newline at end of file +} diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index b589b2e7e1..7a8e89584a 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -84,8 +84,8 @@ namespace osu.Game.Overlays.Mods [BackgroundDependencyLoader] private void load(OsuColour colours) { - Header.Title = ModSelectScreenStrings.ModSelectTitle; - Header.Description = ModSelectScreenStrings.ModSelectDescription; + Header.Title = ModSelectOverlayStrings.ModSelectTitle; + Header.Description = ModSelectOverlayStrings.ModSelectDescription; AddRange(new Drawable[] { @@ -262,7 +262,7 @@ namespace osu.Game.Overlays.Mods { customisationButton = new ShearedToggleButton(BUTTON_WIDTH) { - Text = ModSelectScreenStrings.ModCustomisation, + Text = ModSelectOverlayStrings.ModCustomisation, Active = { BindTarget = customisationVisible } }, new ShearedButton(BUTTON_WIDTH) From 62f6caf76dcc836b08a7db054d7f77d149c551c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 11 May 2022 18:04:39 +0200 Subject: [PATCH 117/147] Rename `ModSelect{Screen -> Overlay}` test scenes --- ...eFreeModSelectScreen.cs => TestSceneFreeModSelectOverlay.cs} | 2 +- ...TestSceneModSelectScreen.cs => TestSceneModSelectOverlay.cs} | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) rename osu.Game.Tests/Visual/Multiplayer/{TestSceneFreeModSelectScreen.cs => TestSceneFreeModSelectOverlay.cs} (98%) rename osu.Game.Tests/Visual/UserInterface/{TestSceneModSelectScreen.cs => TestSceneModSelectOverlay.cs} (99%) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneFreeModSelectScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneFreeModSelectOverlay.cs similarity index 98% rename from osu.Game.Tests/Visual/Multiplayer/TestSceneFreeModSelectScreen.cs rename to osu.Game.Tests/Visual/Multiplayer/TestSceneFreeModSelectOverlay.cs index 9a24a1c646..f40c31b07f 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneFreeModSelectScreen.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneFreeModSelectOverlay.cs @@ -17,7 +17,7 @@ using osuTK.Input; namespace osu.Game.Tests.Visual.Multiplayer { - public class TestSceneFreeModSelectScreen : MultiplayerTestScene + public class TestSceneFreeModSelectOverlay : MultiplayerTestScene { private FreeModSelectOverlay freeModSelectOverlay; private readonly Bindable>> availableMods = new Bindable>>(); diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectScreen.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs similarity index 99% rename from osu.Game.Tests/Visual/UserInterface/TestSceneModSelectScreen.cs rename to osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs index 2e2ce6edd7..08ad030b39 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectScreen.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs @@ -24,7 +24,7 @@ using osuTK.Input; namespace osu.Game.Tests.Visual.UserInterface { [TestFixture] - public class TestSceneModSelectScreen : OsuManualInputManagerTestScene + public class TestSceneModSelectOverlay : OsuManualInputManagerTestScene { [Resolved] private RulesetStore rulesetStore { get; set; } From 9b7ff9f2ee3d9aee2eb443c1e354024e8eae07ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 11 May 2022 18:05:09 +0200 Subject: [PATCH 118/147] Rename `modSelect{Screen -> Overlay}2` variable --- .../Visual/UserInterface/TestSceneModSelectOverlay.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs index 08ad030b39..fc543d9db7 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs @@ -222,7 +222,7 @@ namespace osu.Game.Tests.Visual.UserInterface public void TestSettingsNotCrossPolluting() { Bindable> selectedMods2 = null; - ModSelectOverlay modSelectScreen2 = null; + ModSelectOverlay modSelectOverlay2 = null; createScreen(); AddStep("select diff adjust", () => SelectedMods.Value = new Mod[] { new OsuModDifficultyAdjust() }); @@ -235,7 +235,7 @@ namespace osu.Game.Tests.Visual.UserInterface AddStep("create second overlay", () => { - Add(modSelectScreen2 = new UserModSelectOverlay().With(d => + Add(modSelectOverlay2 = new UserModSelectOverlay().With(d => { d.Origin = Anchor.TopCentre; d.Anchor = Anchor.TopCentre; @@ -243,7 +243,7 @@ namespace osu.Game.Tests.Visual.UserInterface })); }); - AddStep("show", () => modSelectScreen2.Show()); + AddStep("show", () => modSelectOverlay2.Show()); AddAssert("ensure first is unchanged", () => SelectedMods.Value.OfType().Single().CircleSize.Value == 8); AddAssert("ensure second is default", () => selectedMods2.Value.OfType().Single().CircleSize.Value == null); From ddb2d4eef51220e0f41835ddfe4ec593482279fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 11 May 2022 18:06:09 +0200 Subject: [PATCH 119/147] Rename `FreeModSelect{Screen -> Overlay}` reference in inline comment --- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index 7a8e89584a..b3c3eee15a 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -193,7 +193,7 @@ namespace osu.Game.Overlays.Mods State.BindValueChanged(_ => samplePlaybackDisabled.Value = State.Value == Visibility.Hidden, true); // This is an optimisation to prevent refreshing the available settings controls when it can be - // reasonably assumed that the settings panel is never to be displayed (e.g. FreeModSelectScreen). + // reasonably assumed that the settings panel is never to be displayed (e.g. FreeModSelectOverlay). if (customisationButton != null) ((IBindable>)modSettingsArea.SelectedMods).BindTo(SelectedMods); From 315c67a316343e8935c61e640aa502c418e887cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 11 May 2022 19:59:30 +0200 Subject: [PATCH 120/147] Add failing test case for ruleset without all mod types --- .../UserInterface/TestSceneModSelectOverlay.cs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs index fc543d9db7..0b037a10cd 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs @@ -18,6 +18,7 @@ using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Mods; +using osu.Game.Tests.Mods; using osuTK; using osuTK.Input; @@ -481,6 +482,21 @@ namespace osu.Game.Tests.Visual.UserInterface AddUntilStep("3 columns visible", () => this.ChildrenOfType().Count(col => col.IsPresent) == 3); } + [Test] + public void TestColumnHidingOnRulesetChange() + { + createScreen(); + + changeRuleset(0); + AddAssert("5 columns visible", () => this.ChildrenOfType().Count(col => col.IsPresent) == 5); + + AddStep("change to ruleset without all mod types", () => Ruleset.Value = TestCustomisableModRuleset.CreateTestRulesetInfo()); + AddUntilStep("1 column visible", () => this.ChildrenOfType().Count(col => col.IsPresent) == 1); + + changeRuleset(0); + AddAssert("5 columns visible", () => this.ChildrenOfType().Count(col => col.IsPresent) == 5); + } + private void waitForColumnLoad() => AddUntilStep("all column content loaded", () => modSelectOverlay.ChildrenOfType().Any() && modSelectOverlay.ChildrenOfType().All(column => column.IsLoaded && column.ItemsLoaded)); From 478cfc0b87ac3a6062e5948fe8df496df0fac991 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 11 May 2022 18:26:52 +0200 Subject: [PATCH 121/147] Split model class for mod state --- osu.Game/Overlays/Mods/ModState.cs | 35 ++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 osu.Game/Overlays/Mods/ModState.cs diff --git a/osu.Game/Overlays/Mods/ModState.cs b/osu.Game/Overlays/Mods/ModState.cs new file mode 100644 index 0000000000..8fdd5db00b --- /dev/null +++ b/osu.Game/Overlays/Mods/ModState.cs @@ -0,0 +1,35 @@ +// 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.Bindables; +using osu.Game.Rulesets.Mods; + +namespace osu.Game.Overlays.Mods +{ + /// + /// Wrapper class used to store the current state of a mod shown on the . + /// Used primarily to decouple data from drawable logic. + /// + public class ModState + { + /// + /// The mod that whose state this instance describes. + /// + public Mod Mod { get; } + + /// + /// Whether the mod is currently selected. + /// + public BindableBool Active { get; } = new BindableBool(); + + /// + /// Whether the mod is currently filtered out due to not matching imposed criteria. + /// + public BindableBool Filtered { get; } = new BindableBool(); + + public ModState(Mod mod) + { + Mod = mod; + } + } +} From 74599c9c625ae9902e16dfd80d2132d0fc0051e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 11 May 2022 18:29:14 +0200 Subject: [PATCH 122/147] Use `ModState` in mod panels --- osu.Game/Overlays/Mods/ModPanel.cs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/osu.Game/Overlays/Mods/ModPanel.cs b/osu.Game/Overlays/Mods/ModPanel.cs index 4c4951307d..02b8c195ec 100644 --- a/osu.Game/Overlays/Mods/ModPanel.cs +++ b/osu.Game/Overlays/Mods/ModPanel.cs @@ -28,9 +28,11 @@ namespace osu.Game.Overlays.Mods { public class ModPanel : OsuClickableContainer { - public Mod Mod { get; } - public BindableBool Active { get; } = new BindableBool(); - public BindableBool Filtered { get; } = new BindableBool(); + public Mod Mod => modState.Mod; + public BindableBool Active => modState.Active; + public BindableBool Filtered => modState.Filtered; + + private readonly ModState modState; protected readonly Box Background; protected readonly Container SwitchContainer; @@ -57,7 +59,7 @@ namespace osu.Game.Overlays.Mods public ModPanel(Mod mod) { - Mod = mod; + modState = new ModState(mod); RelativeSizeAxes = Axes.X; Height = 42; From e86444c4bf433a34adce2a62f3f0eae9fc9a12ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 11 May 2022 18:37:31 +0200 Subject: [PATCH 123/147] Hoist `ModState` to column level --- .../Mods/IncompatibilityDisplayingModPanel.cs | 5 +++++ osu.Game/Overlays/Mods/ModColumn.cs | 21 ++++++++++--------- osu.Game/Overlays/Mods/ModPanel.cs | 15 ++++++++----- .../Overlays/Mods/UserModSelectOverlay.cs | 2 +- 4 files changed, 27 insertions(+), 16 deletions(-) diff --git a/osu.Game/Overlays/Mods/IncompatibilityDisplayingModPanel.cs b/osu.Game/Overlays/Mods/IncompatibilityDisplayingModPanel.cs index aeb983d352..34c4458a21 100644 --- a/osu.Game/Overlays/Mods/IncompatibilityDisplayingModPanel.cs +++ b/osu.Game/Overlays/Mods/IncompatibilityDisplayingModPanel.cs @@ -19,6 +19,11 @@ namespace osu.Game.Overlays.Mods [Resolved] private Bindable> selectedMods { get; set; } + public IncompatibilityDisplayingModPanel(ModState modState) + : base(modState) + { + } + public IncompatibilityDisplayingModPanel(Mod mod) : base(mod) { diff --git a/osu.Game/Overlays/Mods/ModColumn.cs b/osu.Game/Overlays/Mods/ModColumn.cs index b32ebb4a5c..c5364fb403 100644 --- a/osu.Game/Overlays/Mods/ModColumn.cs +++ b/osu.Game/Overlays/Mods/ModColumn.cs @@ -20,6 +20,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Events; +using osu.Framework.Lists; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.UserInterface; @@ -83,20 +84,20 @@ namespace osu.Game.Overlays.Mods protected override bool ReceivePositionalInputAtSubTree(Vector2 screenSpacePos) => base.ReceivePositionalInputAtSubTree(screenSpacePos) && Active.Value; - protected virtual ModPanel CreateModPanel(Mod mod) => new ModPanel(mod); + protected virtual ModPanel CreateModPanel(ModState mod) => new ModPanel(mod); private readonly Key[]? toggleKeys; private readonly Bindable>> availableMods = new Bindable>>(); /// - /// All mods that are available for the current ruleset in this particular column. + /// Contains information about state of all mods that are available for the current ruleset in this particular column. /// /// /// Note that the mod instances in this list are owned solely by this column /// (as in, they are locally-managed clones, to ensure proper isolation from any other external instances). /// - private IReadOnlyList localAvailableMods = Array.Empty(); + private IReadOnlyList localAvailableMods = Array.Empty(); private readonly TextFlowContainer headerText; private readonly Box headerBackground; @@ -291,10 +292,10 @@ namespace osu.Game.Overlays.Mods private void updateLocalAvailableMods(bool asyncLoadContent) { var newMods = ModUtils.FlattenMods(availableMods.Value.GetValueOrDefault(ModType) ?? Array.Empty()) - .Select(m => m.DeepClone()) + .Select(m => new ModState(m.DeepClone())) .ToList(); - if (newMods.SequenceEqual(localAvailableMods)) + if (newMods.SequenceEqual(localAvailableMods, new FuncEqualityComparer((x, y) => ReferenceEquals(x.Mod, y.Mod)))) return; localAvailableMods = newMods; @@ -393,18 +394,18 @@ namespace osu.Game.Overlays.Mods var newSelection = new List(); - foreach (var mod in localAvailableMods) + foreach (var modState in localAvailableMods) { - var matchingSelectedMod = mods.SingleOrDefault(selected => selected.GetType() == mod.GetType()); + var matchingSelectedMod = mods.SingleOrDefault(selected => selected.GetType() == modState.GetType()); if (matchingSelectedMod != null) { - mod.CopyFrom(matchingSelectedMod); - newSelection.Add(mod); + modState.Mod.CopyFrom(matchingSelectedMod); + newSelection.Add(modState.Mod); } else { - mod.ResetSettingsToDefaults(); + modState.Mod.ResetSettingsToDefaults(); } } diff --git a/osu.Game/Overlays/Mods/ModPanel.cs b/osu.Game/Overlays/Mods/ModPanel.cs index 02b8c195ec..7010342bd8 100644 --- a/osu.Game/Overlays/Mods/ModPanel.cs +++ b/osu.Game/Overlays/Mods/ModPanel.cs @@ -57,9 +57,9 @@ namespace osu.Game.Overlays.Mods private Sample? sampleOff; private Sample? sampleOn; - public ModPanel(Mod mod) + public ModPanel(ModState modState) { - modState = new ModState(mod); + this.modState = modState; RelativeSizeAxes = Axes.X; Height = 42; @@ -81,7 +81,7 @@ namespace osu.Game.Overlays.Mods SwitchContainer = new Container { RelativeSizeAxes = Axes.Y, - Child = new ModSwitchSmall(mod) + Child = new ModSwitchSmall(Mod) { Anchor = Anchor.Centre, Origin = Anchor.Centre, @@ -117,7 +117,7 @@ namespace osu.Game.Overlays.Mods { new OsuSpriteText { - Text = mod.Name, + Text = Mod.Name, Font = OsuFont.TorusAlternate.With(size: 18, weight: FontWeight.SemiBold), Shear = new Vector2(-ShearedOverlayContainer.SHEAR, 0), Margin = new MarginPadding @@ -127,7 +127,7 @@ namespace osu.Game.Overlays.Mods }, new OsuSpriteText { - Text = mod.Description, + Text = Mod.Description, Font = OsuFont.Default.With(size: 12), RelativeSizeAxes = Axes.X, Truncate = true, @@ -143,6 +143,11 @@ namespace osu.Game.Overlays.Mods Action = Active.Toggle; } + public ModPanel(Mod mod) + : this(new ModState(mod)) + { + } + [BackgroundDependencyLoader(true)] private void load(AudioManager audio, OsuColour colours, ISamplePlaybackDisabler? samplePlaybackDisabler) { diff --git a/osu.Game/Overlays/Mods/UserModSelectOverlay.cs b/osu.Game/Overlays/Mods/UserModSelectOverlay.cs index 8ff5e28c8f..7100446730 100644 --- a/osu.Game/Overlays/Mods/UserModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/UserModSelectOverlay.cs @@ -47,7 +47,7 @@ namespace osu.Game.Overlays.Mods { } - protected override ModPanel CreateModPanel(Mod mod) => new IncompatibilityDisplayingModPanel(mod); + protected override ModPanel CreateModPanel(ModState modState) => new IncompatibilityDisplayingModPanel(modState); } } } From 05a21fbbe0563605fe002dfe49ff1bb7a87632da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 11 May 2022 19:02:45 +0200 Subject: [PATCH 124/147] Hoist `ModState` to overlay level --- osu.Game/Overlays/Mods/ModColumn.cs | 69 ++++++++-------------- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 47 +++++++++++---- 2 files changed, 58 insertions(+), 58 deletions(-) diff --git a/osu.Game/Overlays/Mods/ModColumn.cs b/osu.Game/Overlays/Mods/ModColumn.cs index c5364fb403..8b3896a88c 100644 --- a/osu.Game/Overlays/Mods/ModColumn.cs +++ b/osu.Game/Overlays/Mods/ModColumn.cs @@ -20,13 +20,11 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Events; -using osu.Framework.Lists; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.UserInterface; using osu.Game.Localisation; using osu.Game.Rulesets.Mods; -using osu.Game.Utils; using osuTK; using osuTK.Graphics; using osuTK.Input; @@ -39,6 +37,23 @@ namespace osu.Game.Overlays.Mods public readonly ModType ModType; + private IReadOnlyList availableMods = Array.Empty(); + + /// + /// Sets the list of mods to show in this column. + /// + public IReadOnlyList AvailableMods + { + get => availableMods; + set + { + Debug.Assert(value.All(mod => mod.Mod.Type == ModType)); + + availableMods = value; + asyncLoadPanels(); + } + } + private Func? filter; /// @@ -88,17 +103,6 @@ namespace osu.Game.Overlays.Mods private readonly Key[]? toggleKeys; - private readonly Bindable>> availableMods = new Bindable>>(); - - /// - /// Contains information about state of all mods that are available for the current ruleset in this particular column. - /// - /// - /// Note that the mod instances in this list are owned solely by this column - /// (as in, they are locally-managed clones, to ensure proper isolation from any other external instances). - /// - private IReadOnlyList localAvailableMods = Array.Empty(); - private readonly TextFlowContainer headerText; private readonly Box headerBackground; private readonly Container contentContainer; @@ -258,12 +262,8 @@ namespace osu.Game.Overlays.Mods } [BackgroundDependencyLoader] - private void load(OsuGameBase game, OverlayColourProvider colourProvider, OsuColour colours) + private void load(OverlayColourProvider colourProvider, OsuColour colours) { - availableMods.BindTo(game.AvailableMods); - updateLocalAvailableMods(asyncLoadContent: false); - availableMods.BindValueChanged(_ => updateLocalAvailableMods(asyncLoadContent: true)); - headerBackground.Colour = accentColour = colours.ForModType(ModType); if (toggleAllCheckbox != null) @@ -289,30 +289,13 @@ namespace osu.Game.Overlays.Mods toggleAllCheckbox.LabelText = toggleAllCheckbox.Current.Value ? CommonStrings.DeselectAll : CommonStrings.SelectAll; } - private void updateLocalAvailableMods(bool asyncLoadContent) - { - var newMods = ModUtils.FlattenMods(availableMods.Value.GetValueOrDefault(ModType) ?? Array.Empty()) - .Select(m => new ModState(m.DeepClone())) - .ToList(); - - if (newMods.SequenceEqual(localAvailableMods, new FuncEqualityComparer((x, y) => ReferenceEquals(x.Mod, y.Mod)))) - return; - - localAvailableMods = newMods; - - if (asyncLoadContent) - asyncLoadPanels(); - else - onPanelsLoaded(createPanels()); - } - private CancellationTokenSource? cancellationTokenSource; private void asyncLoadPanels() { cancellationTokenSource?.Cancel(); - var panels = createPanels(); + var panels = availableMods.Select(mod => CreateModPanel(mod).With(panel => panel.Shear = new Vector2(-ShearedOverlayContainer.SHEAR, 0))); Task? loadTask; @@ -324,12 +307,6 @@ namespace osu.Game.Overlays.Mods }); } - private IEnumerable createPanels() - { - var panels = localAvailableMods.Select(mod => CreateModPanel(mod).With(panel => panel.Shear = new Vector2(-ShearedOverlayContainer.SHEAR, 0))); - return panels; - } - private void onPanelsLoaded(IEnumerable loaded) { panelFlow.ChildrenEnumerable = loaded; @@ -394,7 +371,7 @@ namespace osu.Game.Overlays.Mods var newSelection = new List(); - foreach (var modState in localAvailableMods) + foreach (var modState in this.availableMods) { var matchingSelectedMod = mods.SingleOrDefault(selected => selected.GetType() == modState.GetType()); @@ -554,10 +531,10 @@ namespace osu.Game.Overlays.Mods int index = Array.IndexOf(toggleKeys, e.Key); if (index < 0) return false; - var panel = panelFlow.ElementAtOrDefault(index); - if (panel == null || panel.Filtered.Value) return false; + var modState = availableMods.ElementAtOrDefault(index); + if (modState == null || modState.Filtered.Value) return false; - panel.Active.Toggle(); + modState.Active.Toggle(); return true; } diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index b3c3eee15a..ace0576b96 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -22,6 +22,7 @@ using osu.Game.Graphics.UserInterface; using osu.Game.Input.Bindings; using osu.Game.Localisation; using osu.Game.Rulesets.Mods; +using osu.Game.Utils; using osuTK; using osuTK.Input; @@ -47,9 +48,7 @@ namespace osu.Game.Overlays.Mods set { isValidMod = value ?? throw new ArgumentNullException(nameof(value)); - - if (IsLoaded) - updateAvailableMods(); + filterMods(); } } @@ -64,6 +63,9 @@ namespace osu.Game.Overlays.Mods protected virtual IEnumerable CreateFooterButtons() => createDefaultFooterButtons(); + private readonly Bindable>> availableMods = new Bindable>>(); + private readonly Dictionary> localAvailableMods = new Dictionary>(); + private readonly BindableBool customisationVisible = new BindableBool(); private ModSettingsArea modSettingsArea = null!; @@ -82,7 +84,7 @@ namespace osu.Game.Overlays.Mods } [BackgroundDependencyLoader] - private void load(OsuColour colours) + private void load(OsuGameBase game, OsuColour colours) { Header.Title = ModSelectOverlayStrings.ModSelectTitle; Header.Description = ModSelectOverlayStrings.ModSelectDescription; @@ -184,12 +186,16 @@ namespace osu.Game.Overlays.Mods LighterColour = colours.Pink1 }) }; + + availableMods.BindTo(game.AvailableMods); } protected override void LoadComplete() { base.LoadComplete(); + availableMods.BindValueChanged(_ => createLocalMods(), true); + State.BindValueChanged(_ => samplePlaybackDisabled.Value = State.Value == Visibility.Hidden, true); // This is an optimisation to prevent refreshing the available settings controls when it can be @@ -211,8 +217,6 @@ namespace osu.Game.Overlays.Mods customisationVisible.BindValueChanged(_ => updateCustomisationVisualState(), true); - updateAvailableMods(); - // Start scrolled slightly to the right to give the user a sense that // there is more horizontal content available. ScheduleAfterChildren(() => @@ -272,6 +276,31 @@ namespace osu.Game.Overlays.Mods } }; + private void createLocalMods() + { + localAvailableMods.Clear(); + + foreach (var (modType, mods) in availableMods.Value) + { + var modStates = mods.SelectMany(ModUtils.FlattenMod) + .Select(mod => new ModState(mod.DeepClone())) + .ToArray(); + + localAvailableMods[modType] = modStates; + } + + filterMods(); + + foreach (var column in columnFlow.Columns) + column.AvailableMods = localAvailableMods.GetValueOrDefault(column.ModType, Array.Empty()); + } + + private void filterMods() + { + foreach (var modState in localAvailableMods.Values.SelectMany(m => m)) + modState.Filtered.Value = !modState.Mod.HasImplementation || !IsValidMod.Invoke(modState.Mod); + } + private void updateMultiplier() { if (multiplierDisplay == null) @@ -285,12 +314,6 @@ namespace osu.Game.Overlays.Mods multiplierDisplay.Current.Value = multiplier; } - private void updateAvailableMods() - { - foreach (var column in columnFlow.Columns) - column.Filter = m => m.HasImplementation && isValidMod.Invoke(m); - } - private void updateCustomisation(ValueChangedEvent> valueChangedEvent) { if (customisationButton == null) From 11ae1da65a092266786f4eae988bd0713584dbc7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 11 May 2022 19:30:06 +0200 Subject: [PATCH 125/147] Hoist reference replacement logic to overlay level --- osu.Game/Overlays/Mods/ModColumn.cs | 116 ++------------------- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 85 ++++++++++----- 2 files changed, 64 insertions(+), 137 deletions(-) diff --git a/osu.Game/Overlays/Mods/ModColumn.cs b/osu.Game/Overlays/Mods/ModColumn.cs index 8b3896a88c..deaaacd775 100644 --- a/osu.Game/Overlays/Mods/ModColumn.cs +++ b/osu.Game/Overlays/Mods/ModColumn.cs @@ -13,7 +13,6 @@ using Humanizer; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; -using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; @@ -54,49 +53,18 @@ namespace osu.Game.Overlays.Mods } } - private Func? filter; - /// /// A function determining whether each mod in the column should be displayed. /// A return value of means that the mod is not filtered and therefore its corresponding panel should be displayed. /// A return value of means that the mod is filtered out and therefore its corresponding panel should be hidden. /// - public Func? Filter - { - get => filter; - set - { - filter = value; - updateState(); - } - } + public Func? Filter { get; set; } // TODO: remove later /// /// Determines whether this column should accept user input. /// public Bindable Active = new BindableBool(true); - private readonly Bindable allFiltered = new BindableBool(); - - /// - /// True if all of the panels in this column have been filtered out by the current . - /// - public IBindable AllFiltered => allFiltered; - - /// - /// List of mods marked as selected in this column. - /// - /// - /// Note that the mod instances returned by this property are owned solely by this column - /// (as in, they are locally-managed clones, to ensure proper isolation from any other external instances). - /// - public IReadOnlyList SelectedMods { get; private set; } = Array.Empty(); - - /// - /// Invoked when a mod panel has been selected interactively by the user. - /// - public event Action? SelectionChangedByUser; - protected override bool ReceivePositionalInputAtSubTree(Vector2 screenSpacePos) => base.ReceivePositionalInputAtSubTree(screenSpacePos) && Active.Value; protected virtual ModPanel CreateModPanel(ModState mod) => new ModPanel(mod); @@ -299,7 +267,11 @@ namespace osu.Game.Overlays.Mods Task? loadTask; - latestLoadTask = loadTask = LoadComponentsAsync(panels, onPanelsLoaded, (cancellationTokenSource = new CancellationTokenSource()).Token); + latestLoadTask = loadTask = LoadComponentsAsync(panels, loaded => + { + panelFlow.ChildrenEnumerable = loaded; + updateState(); + }, (cancellationTokenSource = new CancellationTokenSource()).Token); loadTask.ContinueWith(_ => { if (loadTask == latestLoadTask) @@ -307,27 +279,9 @@ namespace osu.Game.Overlays.Mods }); } - private void onPanelsLoaded(IEnumerable loaded) - { - panelFlow.ChildrenEnumerable = loaded; - - updateState(); - - foreach (var panel in panelFlow) - { - panel.Active.BindValueChanged(_ => panelStateChanged(panel)); - } - } - private void updateState() { - foreach (var panel in panelFlow) - { - panel.Active.Value = SelectedMods.Contains(panel.Mod); - panel.ApplyFilter(Filter); - } - - allFiltered.Value = panelFlow.All(panel => panel.Filtered.Value); + Alpha = availableMods.All(mod => mod.Filtered.Value) ? 0 : 1; if (toggleAllCheckbox != null && !SelectionAnimationRunning) { @@ -336,62 +290,6 @@ namespace osu.Game.Overlays.Mods } } - /// - /// This flag helps to determine the source of changes to . - /// If the value is false, then are changing due to a user selection on the UI. - /// If the value is true, then are changing due to an external call. - /// - private bool externalSelectionUpdateInProgress; - - private void panelStateChanged(ModPanel panel) - { - if (externalSelectionUpdateInProgress) - return; - - var newSelectedMods = panel.Active.Value - ? SelectedMods.Append(panel.Mod) - : SelectedMods.Except(panel.Mod.Yield()); - - SelectedMods = newSelectedMods.ToArray(); - updateState(); - SelectionChangedByUser?.Invoke(); - } - - /// - /// Adjusts the set of selected mods in this column to match the passed in . - /// - /// - /// This method exists to be able to receive mod instances that come from potentially-external sources and to copy the changes across to this column's state. - /// uses this to substitute any external mod references in - /// to references that are owned by this column. - /// - internal void SetSelection(IReadOnlyList mods) - { - externalSelectionUpdateInProgress = true; - - var newSelection = new List(); - - foreach (var modState in this.availableMods) - { - var matchingSelectedMod = mods.SingleOrDefault(selected => selected.GetType() == modState.GetType()); - - if (matchingSelectedMod != null) - { - modState.Mod.CopyFrom(matchingSelectedMod); - newSelection.Add(modState.Mod); - } - else - { - modState.Mod.ResetSettingsToDefaults(); - } - } - - SelectedMods = newSelection; - updateState(); - - externalSelectionUpdateInProgress = false; - } - #region Bulk select / deselect private const double initial_multiple_selection_delay = 120; diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index ace0576b96..567e91e3fa 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -207,14 +207,9 @@ namespace osu.Game.Overlays.Mods { updateMultiplier(); updateCustomisation(val); - updateSelectionFromBindable(); + updateFromExternalSelection(); }, true); - foreach (var column in columnFlow.Columns) - { - column.SelectionChangedByUser += updateBindableFromSelection; - } - customisationVisible.BindValueChanged(_ => updateCustomisationVisualState(), true); // Start scrolled slightly to the right to give the user a sense that @@ -248,7 +243,6 @@ namespace osu.Game.Overlays.Mods { var column = CreateModColumn(modType, toggleKeys).With(column => { - column.Filter = IsValidMod; // spacing applied here rather than via `columnFlow.Spacing` to avoid uneven gaps when some of the columns are hidden. column.Margin = new MarginPadding { Right = 10 }; }); @@ -286,6 +280,9 @@ namespace osu.Game.Overlays.Mods .Select(mod => new ModState(mod.DeepClone())) .ToArray(); + foreach (var modState in modStates) + modState.Active.BindValueChanged(_ => updateFromInternalSelection()); + localAvailableMods[modType] = modStates; } @@ -362,20 +359,50 @@ namespace osu.Game.Overlays.Mods TopLevelContent.MoveToY(-modAreaHeight, transition_duration, Easing.InOutCubic); } - private void updateSelectionFromBindable() - { - // `SelectedMods` may contain mod references that come from external sources. - // to ensure isolation, first pull in the potentially-external change into the mod columns... - foreach (var column in columnFlow.Columns) - column.SetSelection(SelectedMods.Value); + /// + /// This flag helps to determine the source of changes to . + /// If the value is false, then are changing due to a user selection on the UI. + /// If the value is true, then are changing due to an external change. + /// + private bool externalSelectionUpdateInProgress; - // and then, when done, replace the potentially-external mod references in `SelectedMods` with ones we own. - updateBindableFromSelection(); + private void updateFromExternalSelection() + { + externalSelectionUpdateInProgress = true; + + var newSelection = new List(); + + foreach (var modState in localAvailableMods.SelectMany(pair => pair.Value)) + { + var matchingSelectedMod = SelectedMods.Value.SingleOrDefault(selected => selected.GetType() == modState.Mod.GetType()); + + if (matchingSelectedMod != null) + { + modState.Mod.CopyFrom(matchingSelectedMod); + modState.Active.Value = true; + newSelection.Add(modState.Mod); + } + else + { + modState.Mod.ResetSettingsToDefaults(); + modState.Active.Value = false; + } + } + + SelectedMods.Value = newSelection; + + externalSelectionUpdateInProgress = false; } - private void updateBindableFromSelection() + private void updateFromInternalSelection() { - var candidateSelection = columnFlow.Columns.SelectMany(column => column.SelectedMods).ToArray(); + if (externalSelectionUpdateInProgress) + return; + + var candidateSelection = localAvailableMods.SelectMany(pair => pair.Value) + .Where(modState => modState.Active.Value) + .Select(modState => modState.Mod) + .ToArray(); // the following guard intends to check cases where we've already replaced potentially-external mod references with our own and avoid endless recursion. // TODO: replace custom comparer with System.Collections.Generic.ReferenceEqualityComparer when fully on .NET 6 @@ -406,10 +433,12 @@ namespace osu.Game.Overlays.Mods { var column = columnFlow[i].Column; - double delay = column.AllFiltered.Value ? 0 : nonFilteredColumnCount * 30; - double duration = column.AllFiltered.Value ? 0 : fade_in_duration; + bool allFiltered = column.AvailableMods.All(modState => modState.Filtered.Value); + + double delay = allFiltered ? 0 : nonFilteredColumnCount * 30; + double duration = allFiltered ? 0 : fade_in_duration; float startingYPosition = 0; - if (!column.AllFiltered.Value) + if (!allFiltered) startingYPosition = nonFilteredColumnCount % 2 == 0 ? -distance : distance; column.TopLevelContent @@ -418,7 +447,7 @@ namespace osu.Game.Overlays.Mods .MoveToY(0, duration, Easing.OutQuint) .FadeIn(duration, Easing.OutQuint); - if (!column.AllFiltered.Value) + if (!allFiltered) nonFilteredColumnCount += 1; } } @@ -439,9 +468,11 @@ namespace osu.Game.Overlays.Mods { var column = columnFlow[i].Column; - double duration = column.AllFiltered.Value ? 0 : fade_out_duration; + bool allFiltered = column.AvailableMods.All(modState => modState.Filtered.Value); + + double duration = allFiltered ? 0 : fade_out_duration; float newYPosition = 0; - if (!column.AllFiltered.Value) + if (!allFiltered) newYPosition = nonFilteredColumnCount % 2 == 0 ? -distance : distance; column.FlushPendingSelections(); @@ -449,7 +480,7 @@ namespace osu.Game.Overlays.Mods .MoveToY(newYPosition, duration, Easing.OutQuint) .FadeOut(duration, Easing.OutQuint); - if (!column.AllFiltered.Value) + if (!allFiltered) nonFilteredColumnCount += 1; } } @@ -593,8 +624,8 @@ namespace osu.Game.Overlays.Mods protected override void LoadComplete() { base.LoadComplete(); - Active.BindValueChanged(_ => updateState()); - Column.AllFiltered.BindValueChanged(_ => updateState(), true); + + Active.BindValueChanged(_ => updateState(), true); FinishTransforms(); } @@ -604,8 +635,6 @@ namespace osu.Game.Overlays.Mods { Colour4 targetColour; - Column.Alpha = Column.AllFiltered.Value ? 0 : 1; - if (Column.Active.Value) targetColour = Colour4.White; else From 83ba06e7afc42d59273515a1a8b277daeca64af6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 11 May 2022 19:33:52 +0200 Subject: [PATCH 126/147] Extract helper property for accessing all mods --- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index 567e91e3fa..7e28ab4b1f 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -65,6 +65,7 @@ namespace osu.Game.Overlays.Mods private readonly Bindable>> availableMods = new Bindable>>(); private readonly Dictionary> localAvailableMods = new Dictionary>(); + private IEnumerable allLocalAvailableMods => localAvailableMods.SelectMany(pair => pair.Value); private readonly BindableBool customisationVisible = new BindableBool(); @@ -294,7 +295,7 @@ namespace osu.Game.Overlays.Mods private void filterMods() { - foreach (var modState in localAvailableMods.Values.SelectMany(m => m)) + foreach (var modState in allLocalAvailableMods) modState.Filtered.Value = !modState.Mod.HasImplementation || !IsValidMod.Invoke(modState.Mod); } @@ -372,7 +373,7 @@ namespace osu.Game.Overlays.Mods var newSelection = new List(); - foreach (var modState in localAvailableMods.SelectMany(pair => pair.Value)) + foreach (var modState in allLocalAvailableMods) { var matchingSelectedMod = SelectedMods.Value.SingleOrDefault(selected => selected.GetType() == modState.Mod.GetType()); @@ -399,10 +400,9 @@ namespace osu.Game.Overlays.Mods if (externalSelectionUpdateInProgress) return; - var candidateSelection = localAvailableMods.SelectMany(pair => pair.Value) - .Where(modState => modState.Active.Value) - .Select(modState => modState.Mod) - .ToArray(); + var candidateSelection = allLocalAvailableMods.Where(modState => modState.Active.Value) + .Select(modState => modState.Mod) + .ToArray(); // the following guard intends to check cases where we've already replaced potentially-external mod references with our own and avoid endless recursion. // TODO: replace custom comparer with System.Collections.Generic.ReferenceEqualityComparer when fully on .NET 6 From fc24a564782aa31381b8eada9e547f871f5c0978 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 11 May 2022 19:40:56 +0200 Subject: [PATCH 127/147] Add protection from recursive updates from external selection --- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index 7e28ab4b1f..e478b2afcd 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -369,6 +369,9 @@ namespace osu.Game.Overlays.Mods private void updateFromExternalSelection() { + if (externalSelectionUpdateInProgress) + return; + externalSelectionUpdateInProgress = true; var newSelection = new List(); From 7ac6073f13cf31bf6e15489c9f54f31ed87503c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 11 May 2022 20:23:24 +0200 Subject: [PATCH 128/147] Fix column test scene to work --- .../UserInterface/TestSceneModColumn.cs | 59 +++++++++++-------- 1 file changed, 36 insertions(+), 23 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModColumn.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModColumn.cs index e47ae860c6..331509e10f 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModColumn.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModColumn.cs @@ -1,6 +1,8 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +#nullable enable + using System; using System.Linq; using NUnit.Framework; @@ -12,11 +14,9 @@ using osu.Framework.Testing; using osu.Game.Graphics.UserInterface; using osu.Game.Overlays; using osu.Game.Overlays.Mods; -using osu.Game.Rulesets.Catch; -using osu.Game.Rulesets.Mania; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu; -using osu.Game.Rulesets.Taiko; +using osu.Game.Utils; using osuTK.Input; namespace osu.Game.Tests.Visual.UserInterface @@ -41,20 +41,16 @@ namespace osu.Game.Tests.Visual.UserInterface Child = new ModColumn(modType, false) { Anchor = Anchor.Centre, - Origin = Anchor.Centre + Origin = Anchor.Centre, + AvailableMods = getExampleModsFor(modType) } }); - - AddStep("change ruleset to osu!", () => Ruleset.Value = new OsuRuleset().RulesetInfo); - AddStep("change ruleset to taiko", () => Ruleset.Value = new TaikoRuleset().RulesetInfo); - AddStep("change ruleset to catch", () => Ruleset.Value = new CatchRuleset().RulesetInfo); - AddStep("change ruleset to mania", () => Ruleset.Value = new ManiaRuleset().RulesetInfo); } [Test] public void TestMultiSelection() { - ModColumn column = null; + ModColumn column = null!; AddStep("create content", () => Child = new Container { RelativeSizeAxes = Axes.Both, @@ -62,7 +58,8 @@ namespace osu.Game.Tests.Visual.UserInterface Child = column = new ModColumn(ModType.DifficultyIncrease, true) { Anchor = Anchor.Centre, - Origin = Anchor.Centre + Origin = Anchor.Centre, + AvailableMods = getExampleModsFor(ModType.DifficultyIncrease) } }); @@ -91,7 +88,7 @@ namespace osu.Game.Tests.Visual.UserInterface [Test] public void TestFiltering() { - TestModColumn column = null; + TestModColumn column = null!; AddStep("create content", () => Child = new Container { @@ -100,30 +97,31 @@ namespace osu.Game.Tests.Visual.UserInterface Child = column = new TestModColumn(ModType.Fun, true) { Anchor = Anchor.Centre, - Origin = Anchor.Centre + Origin = Anchor.Centre, + AvailableMods = getExampleModsFor(ModType.Fun) } }); - AddStep("set filter", () => column.Filter = mod => mod.Name.Contains("Wind", StringComparison.CurrentCultureIgnoreCase)); + AddStep("set filter", () => setFilter(mod => mod.Name.Contains("Wind", StringComparison.CurrentCultureIgnoreCase))); AddUntilStep("two panels visible", () => column.ChildrenOfType().Count(panel => !panel.Filtered.Value) == 2); clickToggle(); AddUntilStep("wait for animation", () => !column.SelectionAnimationRunning); AddAssert("only visible items selected", () => column.ChildrenOfType().Where(panel => panel.Active.Value).All(panel => !panel.Filtered.Value)); - AddStep("unset filter", () => column.Filter = null); + AddStep("unset filter", () => setFilter(null)); AddUntilStep("all panels visible", () => column.ChildrenOfType().All(panel => !panel.Filtered.Value)); AddAssert("checkbox not selected", () => !column.ChildrenOfType().Single().Current.Value); - AddStep("set filter", () => column.Filter = mod => mod.Name.Contains("Wind", StringComparison.CurrentCultureIgnoreCase)); + AddStep("set filter", () => setFilter(mod => mod.Name.Contains("Wind", StringComparison.CurrentCultureIgnoreCase))); AddUntilStep("two panels visible", () => column.ChildrenOfType().Count(panel => !panel.Filtered.Value) == 2); AddAssert("checkbox selected", () => column.ChildrenOfType().Single().Current.Value); - AddStep("filter out everything", () => column.Filter = _ => false); + AddStep("filter out everything", () => setFilter(_ => false)); AddUntilStep("no panels visible", () => column.ChildrenOfType().All(panel => panel.Filtered.Value)); AddUntilStep("checkbox hidden", () => !column.ChildrenOfType().Single().IsPresent); - AddStep("inset filter", () => column.Filter = null); + AddStep("inset filter", () => setFilter(null)); AddUntilStep("all panels visible", () => column.ChildrenOfType().All(panel => !panel.Filtered.Value)); AddUntilStep("checkbox visible", () => column.ChildrenOfType().Single().IsPresent); @@ -138,7 +136,7 @@ namespace osu.Game.Tests.Visual.UserInterface [Test] public void TestKeyboardSelection() { - ModColumn column = null; + ModColumn column = null!; AddStep("create content", () => Child = new Container { RelativeSizeAxes = Axes.Both, @@ -146,7 +144,8 @@ namespace osu.Game.Tests.Visual.UserInterface Child = column = new ModColumn(ModType.DifficultyReduction, true, new[] { Key.Q, Key.W, Key.E, Key.R, Key.T, Key.Y, Key.U, Key.I, Key.O, Key.P }) { Anchor = Anchor.Centre, - Origin = Anchor.Centre + Origin = Anchor.Centre, + AvailableMods = getExampleModsFor(ModType.DifficultyReduction) } }); @@ -158,7 +157,7 @@ namespace osu.Game.Tests.Visual.UserInterface AddStep("press W again", () => InputManager.Key(Key.W)); AddAssert("NF panel deselected", () => !this.ChildrenOfType().Single(panel => panel.Mod.Acronym == "NF").Active.Value); - AddStep("set filter to NF", () => column.Filter = mod => mod.Acronym == "NF"); + AddStep("set filter to NF", () => setFilter(mod => mod.Acronym == "NF")); AddStep("press W", () => InputManager.Key(Key.W)); AddAssert("NF panel selected", () => this.ChildrenOfType().Single(panel => panel.Mod.Acronym == "NF").Active.Value); @@ -166,12 +165,18 @@ namespace osu.Game.Tests.Visual.UserInterface AddStep("press W again", () => InputManager.Key(Key.W)); AddAssert("NF panel deselected", () => !this.ChildrenOfType().Single(panel => panel.Mod.Acronym == "NF").Active.Value); - AddStep("filter out everything", () => column.Filter = _ => false); + AddStep("filter out everything", () => setFilter(_ => false)); AddStep("press W", () => InputManager.Key(Key.W)); AddAssert("NF panel not selected", () => !this.ChildrenOfType().Single(panel => panel.Mod.Acronym == "NF").Active.Value); - AddStep("clear filter", () => column.Filter = null); + AddStep("clear filter", () => setFilter(null)); + } + + private void setFilter(Func? filter) + { + foreach (var modState in this.ChildrenOfType().Single().AvailableMods) + modState.Filtered.Value = filter?.Invoke(modState.Mod) == false; } private class TestModColumn : ModColumn @@ -183,5 +188,13 @@ namespace osu.Game.Tests.Visual.UserInterface { } } + + private static ModState[] getExampleModsFor(ModType modType) + { + return new OsuRuleset().GetModsFor(modType) + .SelectMany(ModUtils.FlattenMod) + .Select(mod => new ModState(mod)) + .ToArray(); + } } } From 52bbce12f16d3ba769c8da6fa6ee6a51a8a65d07 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 11 May 2022 20:23:56 +0200 Subject: [PATCH 129/147] Fix not being able to set `AvailableMods` before loaded --- osu.Game/Overlays/Mods/ModColumn.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Mods/ModColumn.cs b/osu.Game/Overlays/Mods/ModColumn.cs index deaaacd775..06e96afdca 100644 --- a/osu.Game/Overlays/Mods/ModColumn.cs +++ b/osu.Game/Overlays/Mods/ModColumn.cs @@ -49,7 +49,9 @@ namespace osu.Game.Overlays.Mods Debug.Assert(value.All(mod => mod.Mod.Type == ModType)); availableMods = value; - asyncLoadPanels(); + + if (IsLoaded) + asyncLoadPanels(); } } @@ -249,6 +251,7 @@ namespace osu.Game.Overlays.Mods base.LoadComplete(); toggleAllCheckbox?.Current.BindValueChanged(_ => updateToggleAllText(), true); + asyncLoadPanels(); } private void updateToggleAllText() From b5a9f1310a706491c6019b2a2d159583620f4972 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 11 May 2022 20:28:11 +0200 Subject: [PATCH 130/147] Fix select/deselect all toggle not working correctly after changes --- osu.Game/Overlays/Mods/ModColumn.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/osu.Game/Overlays/Mods/ModColumn.cs b/osu.Game/Overlays/Mods/ModColumn.cs index 06e96afdca..c1904a0bc0 100644 --- a/osu.Game/Overlays/Mods/ModColumn.cs +++ b/osu.Game/Overlays/Mods/ModColumn.cs @@ -50,6 +50,14 @@ namespace osu.Game.Overlays.Mods availableMods = value; + foreach (var mod in availableMods) + { + mod.Active.BindValueChanged(_ => updateState()); + mod.Filtered.BindValueChanged(_ => updateState()); + } + + updateState(); + if (IsLoaded) asyncLoadPanels(); } From 1c0166367d1f12cfaf5194f467d7c64220e1409b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 11 May 2022 20:29:28 +0200 Subject: [PATCH 131/147] Fix remaining column operations being coupled to drawables --- osu.Game/Overlays/Mods/ModColumn.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Overlays/Mods/ModColumn.cs b/osu.Game/Overlays/Mods/ModColumn.cs index c1904a0bc0..d7ce08e124 100644 --- a/osu.Game/Overlays/Mods/ModColumn.cs +++ b/osu.Game/Overlays/Mods/ModColumn.cs @@ -296,8 +296,8 @@ namespace osu.Game.Overlays.Mods if (toggleAllCheckbox != null && !SelectionAnimationRunning) { - toggleAllCheckbox.Alpha = panelFlow.Any(panel => !panel.Filtered.Value) ? 1 : 0; - toggleAllCheckbox.Current.Value = panelFlow.Where(panel => !panel.Filtered.Value).All(panel => panel.Active.Value); + toggleAllCheckbox.Alpha = availableMods.Any(panel => !panel.Filtered.Value) ? 1 : 0; + toggleAllCheckbox.Current.Value = availableMods.Where(panel => !panel.Filtered.Value).All(panel => panel.Active.Value); } } @@ -342,7 +342,7 @@ namespace osu.Game.Overlays.Mods { pendingSelectionOperations.Clear(); - foreach (var button in panelFlow.Where(b => !b.Active.Value && !b.Filtered.Value)) + foreach (var button in availableMods.Where(b => !b.Active.Value && !b.Filtered.Value)) pendingSelectionOperations.Enqueue(() => button.Active.Value = true); } @@ -353,7 +353,7 @@ namespace osu.Game.Overlays.Mods { pendingSelectionOperations.Clear(); - foreach (var button in panelFlow.Where(b => b.Active.Value && !b.Filtered.Value)) + foreach (var button in availableMods.Where(b => b.Active.Value && !b.Filtered.Value)) pendingSelectionOperations.Enqueue(() => button.Active.Value = false); } From 2266a5c9a0588beb5e188197a0dd03c67457dd46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 11 May 2022 20:38:53 +0200 Subject: [PATCH 132/147] Remove no-longer-necessary `ModColumn.Filter` --- osu.Game/Overlays/Mods/ModColumn.cs | 7 ------- 1 file changed, 7 deletions(-) diff --git a/osu.Game/Overlays/Mods/ModColumn.cs b/osu.Game/Overlays/Mods/ModColumn.cs index d7ce08e124..9bb3f8bd9e 100644 --- a/osu.Game/Overlays/Mods/ModColumn.cs +++ b/osu.Game/Overlays/Mods/ModColumn.cs @@ -63,13 +63,6 @@ namespace osu.Game.Overlays.Mods } } - /// - /// A function determining whether each mod in the column should be displayed. - /// A return value of means that the mod is not filtered and therefore its corresponding panel should be displayed. - /// A return value of means that the mod is filtered out and therefore its corresponding panel should be hidden. - /// - public Func? Filter { get; set; } // TODO: remove later - /// /// Determines whether this column should accept user input. /// From 93539160adb48a6be40d69ed33c216d46f52327e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 11 May 2022 21:57:57 +0200 Subject: [PATCH 133/147] Remove no-longer-necessary guard --- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index e478b2afcd..d94f663962 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -12,7 +12,6 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Input.Events; -using osu.Framework.Lists; using osu.Framework.Utils; using osu.Game.Audio; using osu.Game.Configuration; @@ -407,11 +406,6 @@ namespace osu.Game.Overlays.Mods .Select(modState => modState.Mod) .ToArray(); - // the following guard intends to check cases where we've already replaced potentially-external mod references with our own and avoid endless recursion. - // TODO: replace custom comparer with System.Collections.Generic.ReferenceEqualityComparer when fully on .NET 6 - if (candidateSelection.SequenceEqual(SelectedMods.Value, new FuncEqualityComparer(ReferenceEquals))) - return; - SelectedMods.Value = ComputeNewModsFromSelection(SelectedMods.Value, candidateSelection); } From 981ead68bf8a54ece314e00bf0d68a16aa331622 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 11 May 2022 22:20:00 +0200 Subject: [PATCH 134/147] Ensure local mods are constructed in time for `Pop{In,Out}()` --- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index d94f663962..d068839ab0 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -192,10 +192,11 @@ namespace osu.Game.Overlays.Mods protected override void LoadComplete() { - base.LoadComplete(); - + // this is called before base call so that the mod state is populated early, and the transition in `PopIn()` can play out properly. availableMods.BindValueChanged(_ => createLocalMods(), true); + base.LoadComplete(); + State.BindValueChanged(_ => samplePlaybackDisabled.Value = State.Value == Visibility.Hidden, true); // This is an optimisation to prevent refreshing the available settings controls when it can be From cced8609f6d2190156d24c75d5a048166761eb74 Mon Sep 17 00:00:00 2001 From: Jai Sharma Date: Wed, 11 May 2022 22:51:15 +0100 Subject: [PATCH 135/147] Add `setCurrent` param to `ChannelManager.JoinChannel` --- osu.Game/Online/Chat/ChannelManager.cs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/osu.Game/Online/Chat/ChannelManager.cs b/osu.Game/Online/Chat/ChannelManager.cs index 47e45e67d1..1fe784f68b 100644 --- a/osu.Game/Online/Chat/ChannelManager.cs +++ b/osu.Game/Online/Chat/ChannelManager.cs @@ -420,10 +420,11 @@ namespace osu.Game.Online.Chat /// Joins a channel if it has not already been joined. Must be called from the update thread. /// /// The channel to join. + /// Set the channel to join as the current channel if the current channel is null. /// The joined channel. Note that this may not match the parameter channel as it is a backed object. - public Channel JoinChannel(Channel channel) => joinChannel(channel, true); + public Channel JoinChannel(Channel channel, bool setCurrent = true) => joinChannel(channel, true, setCurrent); - private Channel joinChannel(Channel channel, bool fetchInitialMessages = false) + private Channel joinChannel(Channel channel, bool fetchInitialMessages = false, bool setCurrent = true) { if (channel == null) return null; @@ -439,7 +440,7 @@ namespace osu.Game.Online.Chat case ChannelType.Multiplayer: // join is implicit. happens when you join a multiplayer game. // this will probably change in the future. - joinChannel(channel, fetchInitialMessages); + joinChannel(channel, fetchInitialMessages, setCurrent); return channel; case ChannelType.PM: @@ -460,7 +461,7 @@ namespace osu.Game.Online.Chat default: var req = new JoinChannelRequest(channel); - req.Success += () => joinChannel(channel, fetchInitialMessages); + req.Success += () => joinChannel(channel, fetchInitialMessages, setCurrent); req.Failure += ex => LeaveChannel(channel); api.Queue(req); return channel; @@ -472,7 +473,8 @@ namespace osu.Game.Online.Chat this.fetchInitialMessages(channel); } - CurrentChannel.Value ??= channel; + if (setCurrent) + CurrentChannel.Value ??= channel; return channel; } From db371ab0685621d868aa9bb036e963f487ee1a6a Mon Sep 17 00:00:00 2001 From: Jai Sharma Date: Wed, 11 May 2022 22:52:15 +0100 Subject: [PATCH 136/147] Use `CurrentChannel == null` to show the channel selector --- .../Overlays/Chat/ChannelList/ChannelList.cs | 8 ++--- .../Chat/ChannelList/ChannelListItem.cs | 5 +--- .../Chat/ChannelList/ChannelListSelector.cs | 12 ++++---- osu.Game/Overlays/ChatOverlayV2.cs | 29 ++----------------- 4 files changed, 12 insertions(+), 42 deletions(-) diff --git a/osu.Game/Overlays/Chat/ChannelList/ChannelList.cs b/osu.Game/Overlays/Chat/ChannelList/ChannelList.cs index 076dc5719e..6bdf5ee084 100644 --- a/osu.Game/Overlays/Chat/ChannelList/ChannelList.cs +++ b/osu.Game/Overlays/Chat/ChannelList/ChannelList.cs @@ -6,7 +6,6 @@ using System; using System.Collections.Generic; using osu.Framework.Allocation; -using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; @@ -19,11 +18,9 @@ namespace osu.Game.Overlays.Chat.ChannelList { public class ChannelList : Container { - public Action? OnRequestSelect; + public Action? OnRequestSelect; public Action? OnRequestLeave; - public readonly BindableBool SelectorActive = new BindableBool(); - private readonly Dictionary channelMap = new Dictionary(); private ChannelListItemFlow publicChannelFlow = null!; @@ -56,7 +53,7 @@ namespace osu.Game.Overlays.Chat.ChannelList new ChannelListSelector { Margin = new MarginPadding { Bottom = 10 }, - SelectorActive = { BindTarget = SelectorActive }, + Action = () => OnRequestSelect?.Invoke(null), }, privateChannelFlow = new ChannelListItemFlow("DIRECT MESSAGES"), }, @@ -73,7 +70,6 @@ namespace osu.Game.Overlays.Chat.ChannelList ChannelListItem item = new ChannelListItem(channel); item.OnRequestSelect += chan => OnRequestSelect?.Invoke(chan); item.OnRequestLeave += chan => OnRequestLeave?.Invoke(chan); - item.SelectorActive.BindTarget = SelectorActive; ChannelListItemFlow flow = getFlowForChannel(channel); channelMap.Add(channel, item); diff --git a/osu.Game/Overlays/Chat/ChannelList/ChannelListItem.cs b/osu.Game/Overlays/Chat/ChannelList/ChannelListItem.cs index c5ac87f527..dd571c9ad9 100644 --- a/osu.Game/Overlays/Chat/ChannelList/ChannelListItem.cs +++ b/osu.Game/Overlays/Chat/ChannelList/ChannelListItem.cs @@ -31,8 +31,6 @@ namespace osu.Game.Overlays.Chat.ChannelList public readonly BindableBool Unread = new BindableBool(); - public readonly BindableBool SelectorActive = new BindableBool(); - private Box hoverBox = null!; private Box selectBox = null!; private OsuSpriteText text = null!; @@ -127,7 +125,6 @@ namespace osu.Game.Overlays.Chat.ChannelList base.LoadComplete(); selectedChannel.BindValueChanged(_ => updateState(), true); - SelectorActive.BindValueChanged(_ => updateState(), true); Unread.BindValueChanged(_ => updateState(), true); } @@ -163,7 +160,7 @@ namespace osu.Game.Overlays.Chat.ChannelList private void updateState() { - bool selected = selectedChannel.Value == Channel && !SelectorActive.Value; + bool selected = selectedChannel.Value == Channel; if (selected) selectBox.FadeIn(300, Easing.OutQuint); diff --git a/osu.Game/Overlays/Chat/ChannelList/ChannelListSelector.cs b/osu.Game/Overlays/Chat/ChannelList/ChannelListSelector.cs index 9cba93ffa5..7beef7b16c 100644 --- a/osu.Game/Overlays/Chat/ChannelList/ChannelListSelector.cs +++ b/osu.Game/Overlays/Chat/ChannelList/ChannelListSelector.cs @@ -12,17 +12,19 @@ using osu.Framework.Input.Events; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; +using osu.Game.Online.Chat; namespace osu.Game.Overlays.Chat.ChannelList { public class ChannelListSelector : OsuClickableContainer { - public readonly BindableBool SelectorActive = new BindableBool(); - private Box hoverBox = null!; private Box selectBox = null!; private OsuSpriteText text = null!; + [Resolved] + private Bindable currentChannel { get; set; } = null!; + [Resolved] private OverlayColourProvider colourProvider { get; set; } = null!; @@ -69,9 +71,9 @@ namespace osu.Game.Overlays.Chat.ChannelList { base.LoadComplete(); - SelectorActive.BindValueChanged(selector => + currentChannel.BindValueChanged(channel => { - if (selector.NewValue) + if (channel.NewValue == null) { text.FadeColour(colourProvider.Content1, 300, Easing.OutQuint); selectBox.FadeIn(300, Easing.OutQuint); @@ -82,8 +84,6 @@ namespace osu.Game.Overlays.Chat.ChannelList selectBox.FadeOut(200, Easing.OutQuint); } }, true); - - Action = () => SelectorActive.Value = true; } protected override bool OnHover(HoverEvent e) diff --git a/osu.Game/Overlays/ChatOverlayV2.cs b/osu.Game/Overlays/ChatOverlayV2.cs index e59bee7977..19609f0702 100644 --- a/osu.Game/Overlays/ChatOverlayV2.cs +++ b/osu.Game/Overlays/ChatOverlayV2.cs @@ -100,7 +100,6 @@ namespace osu.Game.Overlays RelativeSizeAxes = Axes.Y, Width = side_bar_width, Padding = new MarginPadding { Top = top_bar_height }, - SelectorActive = { BindTarget = selectorActive }, }, new Container { @@ -157,20 +156,10 @@ namespace osu.Game.Overlays channelManager.JoinedChannels.BindCollectionChanged(joinedChannelsChanged, true); channelManager.AvailableChannels.BindCollectionChanged(availableChannelsChanged, true); - channelList.OnRequestSelect += channel => - { - // Manually selecting a channel should dismiss the selector - selectorActive.Value = false; - channelManager.CurrentChannel.Value = channel; - }; + channelList.OnRequestSelect += channel => channelManager.CurrentChannel.Value = channel; channelList.OnRequestLeave += channel => channelManager.LeaveChannel(channel); - channelListing.OnRequestJoin += channel => - { - channelManager.JoinChannel(channel); - // Manually joining a channel should keep the selector open - selectorActive.Value = true; - }; + channelListing.OnRequestJoin += channel => channelManager.JoinChannel(channel, false); channelListing.OnRequestLeave += channel => channelManager.LeaveChannel(channel); textBar.OnSearchTermsChanged += searchTerms => channelListing.SearchTerm = searchTerms; @@ -257,21 +246,9 @@ namespace osu.Game.Overlays loading.Show(); - // Channel is null when leaving the currently selected channel if (newChannel == null) { - // Don't need to autoswitch if the selector is visible - if (selectorActive.Value) - return; - - // Find another channel to switch to - newChannel = channelManager.JoinedChannels.FirstOrDefault(c => c != channel.OldValue); - - if (newChannel == null) - selectorActive.Value = true; - else - currentChannel.Value = newChannel; - + selectorActive.Value = true; return; } From c45e8f619b286b2af42a421f9d0337073b7bf5d4 Mon Sep 17 00:00:00 2001 From: Jai Sharma Date: Wed, 11 May 2022 22:52:25 +0100 Subject: [PATCH 137/147] Update `ChannelList` test scene --- .../Visual/Online/TestSceneChannelList.cs | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneChannelList.cs b/osu.Game.Tests/Visual/Online/TestSceneChannelList.cs index a3bfbd47a3..9929642539 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneChannelList.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneChannelList.cs @@ -25,7 +25,6 @@ namespace osu.Game.Tests.Visual.Online [Cached] private readonly Bindable selected = new Bindable(); - private OsuSpriteText selectorText; private OsuSpriteText selectedText; private OsuSpriteText leaveText; private ChannelList channelList; @@ -43,21 +42,12 @@ namespace osu.Game.Tests.Visual.Online Height = 0.7f, RowDimensions = new[] { - new Dimension(GridSizeMode.Absolute, 20), new Dimension(GridSizeMode.Absolute, 20), new Dimension(GridSizeMode.Absolute, 20), new Dimension(), }, Content = new[] { - new Drawable[] - { - selectorText = new OsuSpriteText - { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - }, - }, new Drawable[] { selectedText = new OsuSpriteText @@ -89,7 +79,6 @@ namespace osu.Game.Tests.Visual.Online channelList.OnRequestSelect += channel => { - channelList.SelectorActive.Value = false; selected.Value = channel; }; @@ -101,12 +90,6 @@ namespace osu.Game.Tests.Visual.Online channelList.RemoveChannel(channel); }; - channelList.SelectorActive.BindValueChanged(change => - { - selectorText.Text = $"Channel Selector Active: {change.NewValue}"; - selected.Value = null; - }, true); - selected.BindValueChanged(change => { selectedText.Text = $"Selected Channel: {change.NewValue?.Name ?? "[null]"}"; From b794deb5c544e5612745e1d33c32fa3122edd7e1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 12 May 2022 12:06:51 +0900 Subject: [PATCH 138/147] Add null checks to screen context insertion --- osu.Game/OsuGame.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 9edcf90132..3d56d33689 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -14,6 +14,7 @@ using osu.Framework.Audio; using osu.Framework.Bindables; using osu.Framework.Configuration; using osu.Framework.Extensions.IEnumerableExtensions; +using osu.Framework.Extensions.TypeExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; @@ -1203,8 +1204,8 @@ namespace osu.Game { scope.Contexts[@"screen stack"] = new { - Current = newScreen.GetType().Name, - Previous = current.GetType().Name, + Current = newScreen?.GetType().ReadableName(), + Previous = current?.GetType().ReadableName(), }; }); From fda61943b074eea3500b32422b032aacf5cac8a0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 12 May 2022 13:04:17 +0900 Subject: [PATCH 139/147] Update distance snap test when cursor at centre of grid to be in line with expectations --- .../Editor/TestSceneOsuDistanceSnapGrid.cs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuDistanceSnapGrid.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuDistanceSnapGrid.cs index 3c3c5cb939..c50aec40a5 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuDistanceSnapGrid.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuDistanceSnapGrid.cs @@ -113,7 +113,14 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor public void TestCursorInCentre() { AddStep("move mouse to centre", () => InputManager.MoveMouseTo(grid.ToScreenSpace(grid_position))); - assertSnappedDistance(0); + assertSnappedDistance(beat_length); + } + + [Test] + public void TestCursorAlmostInCentre() + { + AddStep("move mouse to almost centre", () => InputManager.MoveMouseTo(grid.ToScreenSpace(grid_position) + new Vector2(1))); + assertSnappedDistance(beat_length); } [Test] From f51607521cef17d7ff4ef62082febe76e7252204 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 12 May 2022 13:11:51 +0900 Subject: [PATCH 140/147] Fix distance snap providing zero-distance snaps incorrectly --- .../Edit/Compose/Components/CircularDistanceSnapGrid.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/CircularDistanceSnapGrid.cs b/osu.Game/Screens/Edit/Compose/Components/CircularDistanceSnapGrid.cs index 2c6bb766ad..cb50ea0d4b 100644 --- a/osu.Game/Screens/Edit/Compose/Components/CircularDistanceSnapGrid.cs +++ b/osu.Game/Screens/Edit/Compose/Components/CircularDistanceSnapGrid.cs @@ -7,6 +7,7 @@ using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.UserInterface; +using osu.Framework.Utils; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Objects; using osuTK; @@ -76,14 +77,19 @@ namespace osu.Game.Screens.Edit.Compose.Components Vector2 travelVector = (position - StartPosition); + // We need a non-zero travel vector in order to find a valid direction. if (travelVector == Vector2.Zero) - return (StartPosition, StartTime); + travelVector = new Vector2(0, -1); float travelLength = travelVector.Length; // FindSnappedDistance will always round down, but we want to potentially round upwards. travelLength += DistanceBetweenTicks / 2; + // We never want to snap towards zero. + if (travelLength < DistanceBetweenTicks) + travelLength = DistanceBetweenTicks; + // When interacting with the resolved snap provider, the distance spacing multiplier should first be removed // to allow for snapping at a non-multiplied ratio. float snappedDistance = SnapProvider.FindSnappedDistance(ReferenceObject, travelLength / distanceSpacingMultiplier); From 2dfed6eda17ac3747fe6f3ac6d1406e25ef0a54e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 12 May 2022 13:53:57 +0900 Subject: [PATCH 141/147] Remove `selectorActive` bindable completely --- osu.Game/Overlays/ChatOverlayV2.cs | 34 +++++++++++++----------------- 1 file changed, 15 insertions(+), 19 deletions(-) diff --git a/osu.Game/Overlays/ChatOverlayV2.cs b/osu.Game/Overlays/ChatOverlayV2.cs index 19609f0702..60b25ef52d 100644 --- a/osu.Game/Overlays/ChatOverlayV2.cs +++ b/osu.Game/Overlays/ChatOverlayV2.cs @@ -50,8 +50,6 @@ namespace osu.Game.Overlays private const float side_bar_width = 190; private const float chat_bar_height = 60; - private readonly BindableBool selectorActive = new BindableBool(); - [Resolved] private OsuConfigManager config { get; set; } = null!; @@ -136,7 +134,6 @@ namespace osu.Game.Overlays Anchor = Anchor.BottomRight, Origin = Anchor.BottomRight, Padding = new MarginPadding { Left = side_bar_width }, - ShowSearch = { BindTarget = selectorActive }, }, }; } @@ -145,8 +142,6 @@ namespace osu.Game.Overlays { base.LoadComplete(); - loading.Show(); - config.BindWith(OsuSetting.ChatDisplayHeight, chatHeight); chatHeight.BindValueChanged(height => { Height = height.NewValue; }, true); @@ -164,8 +159,6 @@ namespace osu.Game.Overlays textBar.OnSearchTermsChanged += searchTerms => channelListing.SearchTerm = searchTerms; textBar.OnChatMessageCommitted += handleChatMessage; - - selectorActive.BindValueChanged(v => channelListing.State.Value = v.NewValue ? Visibility.Visible : Visibility.Hidden, true); } /// @@ -244,22 +237,25 @@ namespace osu.Game.Overlays { Channel? newChannel = channel.NewValue; - loading.Show(); - if (newChannel == null) { - selectorActive.Value = true; - return; + // null channel denotes that we should be showing the listing. + channelListing.State.Value = Visibility.Visible; + textBar.ShowSearch.Value = true; } - - selectorActive.Value = false; - - LoadComponentAsync(new DrawableChannel(newChannel), loaded => + else { - currentChannelContainer.Clear(); - currentChannelContainer.Add(loaded); - loading.Hide(); - }); + channelListing.State.Value = Visibility.Hidden; + textBar.ShowSearch.Value = false; + + loading.Show(); + LoadComponentAsync(new DrawableChannel(newChannel), loaded => + { + currentChannelContainer.Clear(); + currentChannelContainer.Add(loaded); + loading.Hide(); + }); + } } private void joinedChannelsChanged(object sender, NotifyCollectionChangedEventArgs args) From 1d38e5bd33ad8764ae3b16b468a967430174f3e3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 12 May 2022 13:58:10 +0900 Subject: [PATCH 142/147] Add notes about weird local handling in `ChannelListSelector` --- osu.Game/Overlays/Chat/ChannelList/ChannelListSelector.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Overlays/Chat/ChannelList/ChannelListSelector.cs b/osu.Game/Overlays/Chat/ChannelList/ChannelListSelector.cs index 7beef7b16c..a07aad2041 100644 --- a/osu.Game/Overlays/Chat/ChannelList/ChannelListSelector.cs +++ b/osu.Game/Overlays/Chat/ChannelList/ChannelListSelector.cs @@ -73,6 +73,8 @@ namespace osu.Game.Overlays.Chat.ChannelList currentChannel.BindValueChanged(channel => { + // This logic should be handled by the chat overlay rather than this component. + // Selected state should be moved to an abstract class and shared with ChannelListItem. if (channel.NewValue == null) { text.FadeColour(colourProvider.Content1, 300, Easing.OutQuint); From c54ca937c505c7056daca10b1fd2f65a938c7d69 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 12 May 2022 14:55:20 +0900 Subject: [PATCH 143/147] Fix CI inspections --- .../Screens/Edit/Compose/Components/CircularDistanceSnapGrid.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/CircularDistanceSnapGrid.cs b/osu.Game/Screens/Edit/Compose/Components/CircularDistanceSnapGrid.cs index cb50ea0d4b..771612fcf1 100644 --- a/osu.Game/Screens/Edit/Compose/Components/CircularDistanceSnapGrid.cs +++ b/osu.Game/Screens/Edit/Compose/Components/CircularDistanceSnapGrid.cs @@ -7,7 +7,6 @@ using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.UserInterface; -using osu.Framework.Utils; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Objects; using osuTK; From 10e41d018af6768090cdcebd41ebdc4a4e6c14c4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 12 May 2022 17:36:35 +0900 Subject: [PATCH 144/147] Fix toggling hit animations on the editor not applying immediately --- osu.Game.Rulesets.Osu/Edit/DrawableOsuEditorRuleset.cs | 9 +++++++++ osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditorRuleset.cs b/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditorRuleset.cs index c89527d8bd..866e41d644 100644 --- a/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditorRuleset.cs +++ b/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditorRuleset.cs @@ -6,6 +6,7 @@ using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; +using osu.Framework.Lists; using osu.Game.Beatmaps; using osu.Game.Configuration; using osu.Game.Rulesets.Mods; @@ -46,15 +47,23 @@ namespace osu.Game.Rulesets.Osu.Edit HitPolicy = new AnyOrderHitPolicy(); } + private readonly WeakList drawableHitObjects = new WeakList(); + [BackgroundDependencyLoader] private void load(OsuConfigManager config) { hitAnimations = config.GetBindable(OsuSetting.EditorHitAnimations); + hitAnimations.BindValueChanged(_ => + { + foreach (var d in drawableHitObjects) d.RefreshStateTransforms(); + }); } protected override void OnNewDrawableHitObject(DrawableHitObject d) { d.ApplyCustomUpdateState += updateState; + + drawableHitObjects.Add(d); } private void updateState(DrawableHitObject hitObject, ArmedState state) diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index 5531bf8b5a..2e573a7f85 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -448,7 +448,7 @@ namespace osu.Game.Rulesets.Objects.Drawables /// /// Reapplies the current . /// - protected void RefreshStateTransforms() => updateState(State.Value, true); + public void RefreshStateTransforms() => updateState(State.Value, true); /// /// Apply (generally fade-in) transforms leading into the start time. From c4854d40048fde964eff9f54e01e60458c052555 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 12 May 2022 17:55:12 +0900 Subject: [PATCH 145/147] Fix slider ball rotation becoming undefined when time is not flowing smoothly --- osu.Game.Rulesets.Osu/Skinning/Default/SliderBall.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/SliderBall.cs b/osu.Game.Rulesets.Osu/Skinning/Default/SliderBall.cs index 8943a91076..710967b741 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Default/SliderBall.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Default/SliderBall.cs @@ -195,16 +195,16 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default public void UpdateProgress(double completionProgress) { - var newPos = drawableSlider.HitObject.CurvePositionAt(completionProgress); + Position = drawableSlider.HitObject.CurvePositionAt(completionProgress); - var diff = lastPosition.HasValue ? lastPosition.Value - newPos : newPos - drawableSlider.HitObject.CurvePositionAt(completionProgress + 0.01f); - if (diff == Vector2.Zero) + var diff = lastPosition.HasValue ? lastPosition.Value - Position : Position - drawableSlider.HitObject.CurvePositionAt(completionProgress + 0.01f); + + // Ensure the value is substantially high enough to allow for Atan2 to get a valid angle. + if (diff.LengthFast < 0.01f) return; - Position = newPos; ball.Rotation = -90 + (float)(-Math.Atan2(diff.X, diff.Y) * 180 / Math.PI); - - lastPosition = newPos; + lastPosition = Position; } private class FollowCircleContainer : CircularContainer From f5649b926a09976f05050534e074f7131c8b46c4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 12 May 2022 18:04:20 +0900 Subject: [PATCH 146/147] Use `AliveObjects` rather than tracking all hitobjects manually --- osu.Game.Rulesets.Osu/Edit/DrawableOsuEditorRuleset.cs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditorRuleset.cs b/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditorRuleset.cs index 866e41d644..72d04d4211 100644 --- a/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditorRuleset.cs +++ b/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditorRuleset.cs @@ -6,7 +6,6 @@ using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; -using osu.Framework.Lists; using osu.Game.Beatmaps; using osu.Game.Configuration; using osu.Game.Rulesets.Mods; @@ -47,23 +46,20 @@ namespace osu.Game.Rulesets.Osu.Edit HitPolicy = new AnyOrderHitPolicy(); } - private readonly WeakList drawableHitObjects = new WeakList(); - [BackgroundDependencyLoader] private void load(OsuConfigManager config) { hitAnimations = config.GetBindable(OsuSetting.EditorHitAnimations); hitAnimations.BindValueChanged(_ => { - foreach (var d in drawableHitObjects) d.RefreshStateTransforms(); + foreach (var d in HitObjectContainer.AliveObjects) + d.RefreshStateTransforms(); }); } protected override void OnNewDrawableHitObject(DrawableHitObject d) { d.ApplyCustomUpdateState += updateState; - - drawableHitObjects.Add(d); } private void updateState(DrawableHitObject hitObject, ArmedState state) From 4463a26f4e3c00287de0532fc14b02fdb3b96724 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 12 May 2022 17:19:07 +0900 Subject: [PATCH 147/147] Refactor opacity computation algorithm --- .../Preprocessing/OsuDifficultyHitObject.cs | 31 ++++++++++++++----- .../Difficulty/Skills/Flashlight.cs | 2 +- osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs | 8 ++--- 3 files changed, 28 insertions(+), 13 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs index 5cbddb5772..cf4802d282 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs @@ -4,6 +4,7 @@ using System; using osu.Game.Rulesets.Difficulty.Preprocessing; using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Osu.Mods; using osu.Game.Rulesets.Osu.Objects; using osuTK; @@ -85,19 +86,33 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing setDistances(clockRate); } - public double Opacity(double mapTime, bool hidden) + public double OpacityAt(double time, bool hidden) { - double ms = BaseObject.StartTime - mapTime; - if (ms < 0) + if (time > BaseObject.StartTime) + { + // Consider a hitobject as being invisible when its start time is passed. + // In reality the hitobject will be visible beyond its start time up until its hittable window has passed, + // but this is an approximation and such a case is unlikely to be hit where this function is used. return 0.0; + } - double preemptTime = BaseObject.TimePreempt; - double fadeInTime = BaseObject.TimeFadeIn; + double fadeInStartTime = BaseObject.StartTime - BaseObject.TimePreempt; + double fadeInDuration = BaseObject.TimeFadeIn; if (hidden) - return Math.Clamp(Math.Min((1.0 - ms / preemptTime) * 2.5, (ms / preemptTime - 0.3) * (1.0 / 0.3)), 0.0, 1.0); - else - return Math.Clamp((preemptTime - ms) / fadeInTime, 0.0, 1.0); + { + // Taken from OsuModHidden. + double fadeOutStartTime = BaseObject.StartTime - BaseObject.TimePreempt + BaseObject.TimeFadeIn; + double fadeOutDuration = BaseObject.TimePreempt * OsuModHidden.FADE_OUT_DURATION_MULTIPLIER; + + return Math.Min + ( + Math.Clamp((time - fadeInStartTime) / fadeInDuration, 0.0, 1.0), + 1.0 - Math.Clamp((time - fadeOutStartTime) / fadeOutDuration, 0.0, 1.0) + ); + } + + return Math.Clamp((time - fadeInStartTime) / fadeInDuration, 0.0, 1.0); } private void setDistances(double clockRate) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs index 5d2052046b..d93007fae5 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs @@ -70,7 +70,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills double stackNerf = Math.Min(1.0, (currentObj.LazyJumpDistance / scalingFactor) / 25.0); // Bonus based on how visible the object is. - double opacityBonus = 1.0 + max_opacity_bonus * (1.0 - osuCurrent.Opacity(currentHitObject.StartTime, hidden)); + double opacityBonus = 1.0 + max_opacity_bonus * (1.0 - osuCurrent.OpacityAt(currentHitObject.StartTime, hidden)); result += stackNerf * opacityBonus * scalingFactor * jumpDistance / cumulativeStrainTime; } diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs b/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs index d602fe67ee..fc04e4d091 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs @@ -27,8 +27,8 @@ namespace osu.Game.Rulesets.Osu.Mods public override Type[] IncompatibleMods => new[] { typeof(IRequiresApproachCircles), typeof(OsuModSpinIn) }; - private const double fade_in_duration_multiplier = 0.4; - private const double fade_out_duration_multiplier = 0.3; + public const double FADE_IN_DURATION_MULTIPLIER = 0.4; + public const double FADE_OUT_DURATION_MULTIPLIER = 0.3; protected override bool IsFirstAdjustableObject(HitObject hitObject) => !(hitObject is Spinner || hitObject is SpinnerTick); @@ -41,7 +41,7 @@ namespace osu.Game.Rulesets.Osu.Mods static void applyFadeInAdjustment(OsuHitObject osuObject) { - osuObject.TimeFadeIn = osuObject.TimePreempt * fade_in_duration_multiplier; + osuObject.TimeFadeIn = osuObject.TimePreempt * FADE_IN_DURATION_MULTIPLIER; foreach (var nested in osuObject.NestedHitObjects.OfType()) applyFadeInAdjustment(nested); } @@ -156,7 +156,7 @@ namespace osu.Game.Rulesets.Osu.Mods static (double fadeStartTime, double fadeDuration) getParameters(OsuHitObject hitObject) { double fadeOutStartTime = hitObject.StartTime - hitObject.TimePreempt + hitObject.TimeFadeIn; - double fadeOutDuration = hitObject.TimePreempt * fade_out_duration_multiplier; + double fadeOutDuration = hitObject.TimePreempt * FADE_OUT_DURATION_MULTIPLIER; // new duration from completed fade in to end (before fading out) double longFadeDuration = hitObject.GetEndTime() - fadeOutStartTime;